Skip to content

Commit b858949

Browse files
Merge pull request #72 from PepperDash/tp-reconnect
tp reconnect
2 parents 3767d6d + 84b678d commit b858949

File tree

3 files changed

+139
-66
lines changed

3 files changed

+139
-66
lines changed

WEBSOCKET_RECONNECTION.md

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,26 @@ This document describes the automatic reconnection logic implemented in the WebS
1010

1111
The WebSocket middleware uses a simple approach: **auto-reconnect for everything except custom application codes**.
1212

13-
| Close Code | Scenario | Behavior |
14-
| ------------------------ | ------------------------ | ------------------------------------------ |
15-
| **4100** | Client-initiated cleanup | No reconnection, clean shutdown |
16-
| **4000** | User code changed | No reconnection, show error, clear state |
17-
| **4002** | Room combination changed | No reconnection, manual reconnect required |
18-
| **4001** (not processor) | Processor shutdown | No reconnection, manual reconnect required |
19-
| **All other codes** | Any disconnect/error | **Auto-reconnect after 5s** |
13+
| Close Code | Scenario | Behavior |
14+
| --------------------------------- | ------------------------ | ------------------------------------------ |
15+
| **4100** | Client-initiated cleanup | No reconnection, clean shutdown |
16+
| **4000** | User code changed | No reconnection, show error, clear state |
17+
| **4002** | Room combination changed | No reconnection, manual reconnect required |
18+
| **4001** (with touchpanel key) | Connection loss | **Auto-reconnect after 5s** |
19+
| **4001** (no touchpanel, no proc) | Processor shutdown | No reconnection, manual reconnect required |
20+
| **4001** (no touchpanel, proc HW) | Connection loss | **Auto-reconnect after 5s** |
21+
| **All other codes** | Any disconnect/error | **Auto-reconnect after 5s** |
2022

2123
**This includes:**
2224

2325
- **1000** (Normal Closure) - Typical processor hardware disconnect
24-
- **4001** on processor hardware - Connection loss
2526
- Any network errors, unexpected disconnects, etc.
2627

27-
**Simple rule**: If it's not one of the three custom codes (4000, 4002, 4100) or 4001 on non-processor hardware, it will auto-reconnect.
28+
**Code 4001 Logic**:
29+
30+
1. **If touchpanel key exists** → Always auto-reconnect (regardless of processor hardware flag)
31+
2. **If no touchpanel key AND not on processor hardware** → Manual reconnect required
32+
3. **If no touchpanel key BUT on processor hardware** → Auto-reconnect
2833

2934
### Processor Hardware Mode
3035

@@ -92,38 +97,49 @@ newWs.onopen = (ev: Event) => {
9297
};
9398
```
9499

95-
#### Special Case: Code 4001 on Non-Processor Hardware
96-
97-
**Scenario**: Server is on a computer/VM that might be shut down
100+
#### Special Case: Code 4001 Based on Touchpanel Key
98101

99-
- Disconnect code 4001 likely means server is intentionally stopping
100-
- Auto-reconnection would be futile
101-
- User should manually reconnect when server restarts
102+
**Scenario**: Code 4001 behavior depends on touchpanel key presence and processor hardware flag
102103

103-
**Behavior on disconnect (code 4001 only)**:
104+
**Priority Logic**:
104105

105-
1. Show error message: "Processor has disconnected. Click Reconnect"
106-
2. Clear all state (full cleanup)
107-
3. Wait for manual user action
108-
4. **No automatic reconnection**
106+
1. **Touchpanel key exists** → Always auto-reconnect (best indicator of active touchpanel)
107+
2. **No touchpanel key + not on processor hardware** → Manual reconnect required
108+
3. **No touchpanel key + on processor hardware** → Auto-reconnect
109109

110110
**Code**:
111111

112112
```typescript
113-
// Code 4001 on non-processor hardware should not auto-reconnect
114-
if (closeEvent.code === 4001 && !serverIsRunningOnProcessorHardware) {
115-
console.log(
116-
'WebSocket middleware: Processor disconnected (not on processor hardware)'
117-
);
118-
dispatch(
119-
uiActions.setErrorMessage('Processor has disconnected. Click Reconnect')
120-
);
121-
clearStateDataOnDisconnect(dispatch);
122-
return; // Early exit - no auto-reconnect
113+
// Handle code 4001 based on touchpanel key presence
114+
if (closeEvent.code === 4001) {
115+
const currentState = getState() as LocalRootState;
116+
const hasTouchpanelKey = !!currentState.runtimeConfig?.touchpanelKey;
117+
118+
if (hasTouchpanelKey) {
119+
console.log(
120+
'WebSocket middleware: Code 4001 received with touchpanel key present, will auto-reconnect'
121+
);
122+
// Will fall through to auto-reconnect logic below
123+
} else if (!serverIsRunningOnProcessorHardware) {
124+
console.log(
125+
'WebSocket middleware: Processor disconnected (no touchpanel key, not on processor hardware)'
126+
);
127+
stopReconnectionLoop();
128+
dispatch(
129+
uiActions.setErrorMessage('Processor has disconnected. Click Reconnect')
130+
);
131+
clearStateDataOnDisconnect(dispatch);
132+
return;
133+
} else {
134+
console.log(
135+
'WebSocket middleware: Code 4001 on processor hardware (no touchpanel key), will auto-reconnect'
136+
);
137+
// Will fall through to auto-reconnect logic below
138+
}
123139
}
124140
```
125141

126-
**Note**: Code 4001 on processor hardware WILL auto-reconnect (treated like any other disconnect).
142+
**Touchpanel Key Priority**: The presence of a touchpanel key indicates an active touchpanel connection that should be maintained, so it takes priority over the processor hardware flag for reconnection decisions.
127143

128144
#### When Server is NOT on Processor Hardware (`serverIsRunningOnProcessorHardware: false`)
129145

@@ -263,11 +279,38 @@ newWs.onclose = (closeEvent: CloseEvent): void => {
263279
};
264280
```
265281

282+
## Anti-Flashing Mechanism
283+
284+
To prevent the UI from flashing between connected/disconnected states during reconnection attempts, the middleware uses a delayed connection state update:
285+
286+
```typescript
287+
newWs.onopen = (ev: Event) => {
288+
console.log('WebSocket middleware: Connected');
289+
state.waitingToReconnect = false;
290+
stopReconnectionLoop();
291+
292+
// Delay setting connected state to avoid flashing during failed reconnection attempts
293+
setTimeout(() => {
294+
// Only set connected if this WebSocket is still the current client
295+
if (state.client === newWs && newWs.readyState === WebSocket.OPEN) {
296+
dispatch(runtimeConfigActions.setWebsocketIsConnected(true));
297+
}
298+
}, 100);
299+
};
300+
```
301+
302+
**Why this prevents flashing**:
303+
304+
- WebSocket `onopen` can fire briefly even for connections that will immediately fail
305+
- Without delay: `onopen``isConnected = true` → UI shows children → `onclose``isConnected = false` → UI shows DisconnectedMessage (flashing)
306+
- With delay: `onopen` → wait 100ms → check if still connected → only then set `isConnected = true`
307+
266308
This ensures:
267309

268310
- Only one connection attempt at a time
269311
- **Client reference is cleared immediately on disconnect**
270312
- Prevents "already connected" errors on reconnection
313+
- **No UI flashing during reconnection attempts**
271314
- No race conditions
272315
- Clean state management
273316

@@ -329,22 +372,33 @@ This is stored in Redux state at `runtimeConfig.serverIsRunningOnProcessorHardwa
329372
4. **Expected**: Shows "Connection lost..."
330373
5. **Expected**: Multiple retry attempts until network is back
331374

332-
### Test 3: Code 4001 on Processor Hardware
375+
### Test 3: Code 4001 with Touchpanel Key Present
333376

334-
1. Set `serverIsRunningOnProcessorHardware: true`
377+
1. Set touchpanel key in runtime config: `runtimeConfig.touchpanelKey = "some-key"`
335378
2. Connect to WebSocket
336379
3. Trigger disconnect with close code 4001
337-
4. **Expected**: Auto-reconnects after 5s
380+
4. **Expected**: Auto-reconnects after 5s (regardless of processor hardware flag)
338381
5. **Expected**: Shows "Connection lost. Attempting to reconnect..."
382+
6. **Expected**: Continuous retry attempts until successful
383+
384+
### Test 4: Code 4001 without Touchpanel Key (Processor Hardware)
339385

340-
### Test 4: Code 4001 on Non-Processor Hardware
386+
1. Set `serverIsRunningOnProcessorHardware: true`
387+
2. Ensure no touchpanel key: `runtimeConfig.touchpanelKey = null`
388+
3. Connect to WebSocket
389+
4. Trigger disconnect with close code 4001
390+
5. **Expected**: Auto-reconnects after 5s
391+
6. **Expected**: Shows "Connection lost. Attempting to reconnect..."
392+
393+
### Test 5: Code 4001 without Touchpanel Key (Non-Processor Hardware)
341394

342395
1. Set `serverIsRunningOnProcessorHardware: false`
343-
2. Connect to WebSocket
344-
3. Stop server with close code 4001
345-
4. **Expected**: Shows "Processor has disconnected. Click Reconnect"
346-
5. **Expected**: No auto-reconnect attempts
347-
6. **Expected**: Manual reconnect button works when server is back
396+
2. Ensure no touchpanel key: `runtimeConfig.touchpanelKey = null`
397+
3. Connect to WebSocket
398+
4. Stop server with close code 4001
399+
5. **Expected**: Shows "Processor has disconnected. Click Reconnect"
400+
6. **Expected**: No auto-reconnect attempts
401+
7. **Expected**: Manual reconnect button works when server is back
348402

349403
### Test 5: User Code Changed (Code 4000)
350404

src/lib/shared/disconnectedMessage/DisconnectedMessage.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,20 @@ const DisconnectedMessage = () => {
1313
const showReconnect = useShowReconnect();
1414

1515
return (
16-
<div className="vh-100 d-flex flex-grow-1 justify-content-center align-items-center">
16+
<div className="vh-100 d-flex flex-column flex-grow-1 justify-content-center align-items-center gap-5 ">
1717
<div className={`${classes.mwfit} mx-auto text-center`}>
1818
{isConnected === undefined ? (
19-
<h3>Connecting...</h3>
19+
<h2>Connecting...</h2>
2020
) : (
21-
<h3>Disconnected</h3>
22-
)}
23-
{errorMessage && <h6>{errorMessage}</h6>}
24-
{showReconnect && (
25-
<button
26-
className="btn btn-secondary btn-lg"
27-
onPointerDown={reconnect}
28-
>
29-
Reconnect
30-
</button>
21+
<h2>Disconnected</h2>
3122
)}
23+
{errorMessage && <h5 className="mt-1">{errorMessage}</h5>}
3224
</div>
25+
{showReconnect && (
26+
<button className="btn btn-secondary btn-lg" onPointerDown={reconnect}>
27+
Reconnect
28+
</button>
29+
)}
3330
</div>
3431
);
3532
};

src/lib/store/middleware/websocketMiddleware.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,14 @@ export const createWebSocketMiddleware = (): Middleware<
313313
console.log('WebSocket middleware: Connected', ev.type, ev.target);
314314
state.waitingToReconnect = false;
315315
stopReconnectionLoop();
316-
dispatch(runtimeConfigActions.setWebsocketIsConnected(true));
316+
317+
// Delay setting connected state to avoid flashing during failed reconnection attempts
318+
setTimeout(() => {
319+
// Only set connected if this WebSocket is still the current client
320+
if (state.client === newWs && newWs.readyState === WebSocket.OPEN) {
321+
dispatch(runtimeConfigActions.setWebsocketIsConnected(true));
322+
}
323+
}, 100);
317324
};
318325

319326
newWs.onerror = (err) => {
@@ -367,19 +374,34 @@ export const createWebSocketMiddleware = (): Middleware<
367374
return;
368375
}
369376

370-
// Code 4001 on non-processor hardware should not auto-reconnect
371-
if (closeEvent.code === 4001 && !serverIsRunningOnProcessorHardware) {
372-
console.log(
373-
'WebSocket middleware: Processor disconnected (not on processor hardware)'
374-
);
375-
stopReconnectionLoop();
376-
dispatch(
377-
uiActions.setErrorMessage(
378-
'Processor has disconnected. Click Reconnect'
379-
)
380-
);
381-
clearStateDataOnDisconnect(dispatch);
382-
return;
377+
// Handle code 4001 based on touchpanel key presence
378+
if (closeEvent.code === 4001) {
379+
const currentState = getState() as LocalRootState;
380+
const hasTouchpanelKey = !!currentState.runtimeConfig?.touchpanelKey;
381+
382+
if (hasTouchpanelKey) {
383+
console.log(
384+
'WebSocket middleware: Code 4001 received with touchpanel key present, will auto-reconnect'
385+
);
386+
// Will fall through to auto-reconnect logic below
387+
} else if (!serverIsRunningOnProcessorHardware) {
388+
console.log(
389+
'WebSocket middleware: Processor disconnected (no touchpanel key, not on processor hardware)'
390+
);
391+
stopReconnectionLoop();
392+
dispatch(
393+
uiActions.setErrorMessage(
394+
'Processor has disconnected. Click Reconnect to continue.'
395+
)
396+
);
397+
clearStateDataOnDisconnect(dispatch);
398+
return;
399+
} else {
400+
console.log(
401+
'WebSocket middleware: Code 4001 on processor hardware (no touchpanel key), will auto-reconnect'
402+
);
403+
// Will fall through to auto-reconnect logic below
404+
}
383405
}
384406

385407
// All other close codes (including 1000 and 4001 on processor hardware) will auto-reconnect

0 commit comments

Comments
 (0)