Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
69dd792
ci: publish new build on non-main branch
andrew-welker Jul 3, 2025
205aba9
fix: /system/roomCombinationChanged handling override
andrew-welker Jul 3, 2025
6b5f13e
fix: and useCameraBase to exports
ndorin Jul 15, 2025
957fc4b
fix: update cameraList type to CameraState[]
ndorin Jul 16, 2025
6e1b172
Merge remote-tracking branch 'origin/main' into feature/export-useCam…
ndorin Jul 16, 2025
fe937b0
feat: add useIHasCameras hook and IHasCamerasState interface
ndorin Jul 16, 2025
79e0e03
fix: extend IHasSelectableItemsState interface to inherit from Device…
ndorin Jul 16, 2025
523092f
feat: add usb support & currentSources interface
andrew-welker Jul 17, 2025
3616093
fix: add currentSources/keys to DisplayState
andrew-welker Jul 17, 2025
0006003
fix: make currentSource{s} optional
andrew-welker Jul 17, 2025
bc1b60b
fix: export actions for use with storybook
andrew-welker Jul 18, 2025
16d948e
chore(force-patch): fix casing issues
andrew-welker Jul 18, 2025
7fa896f
chore(force-patch): update tsconfig.json
andrew-welker Jul 18, 2025
b227432
Merge commit '16d948e3b0f4268874ed845d5dcc4002e406c96d' into feature/…
ndorin Jul 18, 2025
8c327de
fix: export websocket context
andrew-welker Jul 18, 2025
45c0a51
fix: change export style for WebsocketContext
andrew-welker Jul 18, 2025
521e6a8
fix: use correct import in hook
andrew-welker Jul 18, 2025
77b265e
feat: adds SecondaryAudio to SignalType
ndorin Jul 18, 2025
ce8b899
fix: put back missing type exports
andrew-welker Jul 18, 2025
6452cee
Merge commit 'ce8b8998b9d4328144b5870fe07b5bf28f958c03' into feature/…
ndorin Jul 18, 2025
9eac502
fix: use correct types and message format for DSP Preset recall
andrew-welker Jul 21, 2025
ef3a3aa
chore(force-patch): go back to default style.css name
andrew-welker Jul 21, 2025
22a0e93
fix: update css file name and exports
andrew-welker Jul 21, 2025
085ccd5
fix: remove extra .css
andrew-welker Jul 21, 2025
f58d1d1
build: use correct URL for git repo
andrew-welker Jul 21, 2025
89f2f60
fix: make styles export an object
andrew-welker Jul 21, 2025
ca0f04a
Merge remote-tracking branch 'origin/new-types' into feature/export-u…
ndorin Jul 22, 2025
e1aa436
fix: update InputSlot and OutputSlot interfaces for better type safety
ndorin Jul 22, 2025
04adc0e
fix: remove unnecessary import from IHasSelectableItemsState
ndorin Jul 22, 2025
ffe66a1
fix: comment out deprecated state property in DeviceState interface
ndorin Jul 22, 2025
bbf1271
fix: add deviceInterfaceSupport to RoomData interface and initial state
ndorin Sep 24, 2025
8aef1ef
fix: add selectDeviceInterfaceSupport selector and corresponding hook
ndorin Sep 24, 2025
2adf5f8
feat: implement useStateIsSynced hook and related UI state management
ndorin Sep 25, 2025
4dbbfe7
fix: add missing export for useStateIsSynced hook in index.ts
ndorin Sep 25, 2025
adf462a
fix: update syncState to use array instead of Set for consistency in …
ndorin Sep 25, 2025
157e9ce
fix: standardize import quotes and replace onClick with onPointerDown…
ndorin Sep 26, 2025
8a05e1d
fix: clear all modals and sync state on websocket disconnection
ndorin Oct 10, 2025
e0db8c5
fix: refactor websocket disconnection handling to use a dedicated fun…
ndorin Oct 10, 2025
3a7c460
fix: remove dependency on serverIsRunningOnProcessorHardware
andrew-welker Oct 14, 2025
a9474c1
chore(force-patch): disable exhaustive deps
andrew-welker Oct 14, 2025
5f0c9c0
fix: remove dependency for getRoomData callback
andrew-welker Oct 14, 2025
1929d04
fix: add dep on serverIsRunningOnProcessorHardware back in
andrew-welker Oct 14, 2025
9602b71
fix: move check for websocket before getRoomData call
andrew-welker Oct 14, 2025
70c3aa3
feat: websocket is now middleware instead of react component
andrew-welker Oct 15, 2025
a542a72
docs: Copilot refactoring documentation
andrew-welker Oct 15, 2025
6f26859
docs: more copilot docs
andrew-welker Oct 15, 2025
d08694c
fix: ws middleware now reconnects as it should
andrew-welker Oct 15, 2025
2724680
fix: log WebSocket messages only in development mode
ndorin Oct 15, 2025
ffaa327
Merge commit 'd08694c3f85f57ff09cd46aa5d80362eb73919c2' into fix-doub…
ndorin Oct 15, 2025
89f7f79
fix: resolve copilot conversations
andrew-welker Oct 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 267 additions & 0 deletions AUTOMATIC_ROOM_STATUS_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
# Automatic Room Status Request Implementation

## Overview

This document describes the implementation of automatic room status requests in the WebSocket middleware. This eliminates the need for component-based logic to trigger room status requests and centralizes all WebSocket behavior in the Redux middleware layer.

## Implementation Approach: State Change Listeners

We chose **Approach 2** (State Change Listeners) over the component-based approach for the following reasons:

- ✅ Centralizes all WebSocket logic in middleware
- ✅ Eliminates dependency on React component lifecycle
- ✅ More testable (no need to test React hooks)
- ✅ Easier to maintain (single source of truth)
- ✅ More predictable (Redux action flow is explicit)

## Changes Made

### 1. Added `requestRoomStatus()` Helper Function

**Location**: `src/lib/store/middleware/websocketMiddleware.ts` (line ~190)

```typescript
const requestRoomStatus = (getState: () => LocalRootState) => {
const state = getState();
const roomKey = state.runtimeConfig.currentRoomKey;
const clientId = state.runtimeConfig.roomData?.clientId;
const isConnected = state.runtimeConfig.websocket.isConnected;

if (!roomKey || !clientId || !isConnected) {
console.log(
'[WebSocket Middleware] Cannot request room status - missing requirements:',
{ roomKey, clientId, isConnected }
);
return;
}

console.log(
`[WebSocket Middleware] Requesting status for room: ${roomKey} with clientId: ${clientId}`
);

sendMessage(`/room/${roomKey}/status`, null, getState);
};
```

**Purpose**:

- Validates that all required state is available (roomKey, clientId, isConnected)
- Sends a WebSocket message to request room status
- Includes logging for debugging

### 2. Added State Change Listeners in Middleware

**Location**: `src/lib/store/middleware/websocketMiddleware.ts` (in the default case of the switch statement, line ~590)

The middleware now listens for three Redux actions that should trigger room status requests:

#### a) `setWebsocketIsConnected` - Connection Established

```typescript
if (action.type === runtimeConfigActions.setWebsocketIsConnected.type) {
const isConnected = (action as AnyAction).payload;
if (isConnected === true) {
console.log(
'[WebSocket Middleware] Connection established, requesting room status...'
);
setTimeout(() => requestRoomStatus(store.getState), 100);
}
}
```

**Triggers when**: WebSocket connection is successfully established
**Why needed**: Initial room status request after connecting

#### b) `setRoomData` - Room Data/ClientId Available

```typescript
else if (action.type === runtimeConfigActions.setRoomData.type) {
const state = store.getState() as LocalRootState;
const roomData = (action as AnyAction).payload as RoomData | undefined;
if (state.runtimeConfig.websocket.isConnected && roomData?.clientId) {
console.log(
"[WebSocket Middleware] Room data received, requesting room status..."
);
setTimeout(() => requestRoomStatus(store.getState), 100);
}
}
```

**Triggers when**: Room data is received from the `/ui/joinroom` API response (includes clientId)
**Why needed**: ClientId is required for room status requests; this ensures we have it before requesting

#### c) `setCurrentRoomKey` - Room Changed

```typescript
else if (action.type === runtimeConfigActions.setCurrentRoomKey.type) {
const roomKey = (action as AnyAction).payload;
if (roomKey) {
console.log(
"[WebSocket Middleware] Room changed to:",
roomKey,
", requesting room status..."
);
setTimeout(() => requestRoomStatus(store.getState), 100);
}
}
```

**Triggers when**: User navigates to a different room
**Why needed**: Each room needs its status requested separately

## Technical Details

### Why `setTimeout` with 100ms delay?

The 100ms delay ensures that:

1. All Redux state updates have completed
2. The WebSocket connection is fully ready
3. Any pending actions in the queue are processed first

This prevents race conditions where we might try to send a message before the connection is fully established or before all required state is set.

### Type Safety

We use `AnyAction` casting for the Redux action payloads since these actions come from slice reducers (not our custom WebSocket actions). The middleware's switch statement uses type narrowing to safely access the payload.

### State Path Correction

The WebSocket connection state is accessed via `state.runtimeConfig.websocket.isConnected` (not `state.runtimeConfig.websocketIsConnected`).

## Flow Diagram

```
App Startup
├─> User navigates to app with token
├─> WebsocketProvider mounts
│ │
│ └─> dispatch(wsConnect())
│ │
│ ├─> Middleware: initialize() - fetch app config
│ │
│ ├─> Middleware: getRoomData() - call /ui/joinroom API
│ │ │
│ │ └─> dispatch(setRoomData(roomData))
│ │ │
│ │ └─> Listener: setRoomData detected
│ │ │
│ │ └─> requestRoomStatus() [if connected]
│ │
│ └─> Middleware: connect() - establish WebSocket
│ │
│ └─> dispatch(setWebsocketIsConnected(true))
│ │
│ └─> Listener: setWebsocketIsConnected detected
│ │
│ └─> requestRoomStatus()
└─> User navigates to different room
└─> dispatch(setCurrentRoomKey(newRoomKey))
└─> Listener: setCurrentRoomKey detected
└─> requestRoomStatus()
```

## Benefits

1. **Single Source of Truth**: All WebSocket logic is in the middleware
2. **Testability**: Can test middleware independently without React components
3. **Maintainability**: Changes to room status request logic only need to be made in one place
4. **Predictability**: Redux action flow is explicit and easy to trace
5. **Separation of Concerns**: WebsocketProvider component is purely a Context wrapper

## Optional Next Steps

### Remove Component-Based useEffect (Optional)

The `WebsocketProvider.tsx` still has a useEffect that requests room status:

```typescript
useEffect(() => {
if (isConnected && roomKey && clientId) {
console.log('Requesting room status');
sendSimpleMessage(`/room/${roomKey}/status`);
}
}, [isConnected, roomKey, clientId, sendSimpleMessage]);
```

This can now be removed since the middleware handles it automatically. However, keeping it provides a fallback mechanism and doesn't cause any issues (the duplicate request is harmless).

**Recommendation**: Remove it for cleaner code, but it's not required.

## Automatic Reconnection

### Processor Hardware Support

The middleware now supports **automatic reconnection** for servers running on processor hardware:

**When `serverIsRunningOnProcessorHardware` is `true`:**

- Connection drops (close code 4001) are treated as temporary network issues
- Shows "Connection lost. Attempting to reconnect..." message
- Automatically attempts reconnection after 5 seconds
- Continues reconnecting until successful or manually stopped

**When `serverIsRunningOnProcessorHardware` is `false`:**

- Connection drops (close code 4001) are treated as processor shutdown
- Shows "Processor has disconnected. Click Reconnect" message
- Requires manual reconnection by user

**For unexpected disconnects** (other close codes):

- Automatically attempts reconnection after 5 seconds regardless of hardware status
- This handles network blips, server restarts, etc.

**Implementation Details:**

```typescript
setTimeout(() => {
state.waitingToReconnect = false;

if (closeEvent.code === 4001 && serverIsRunningOnProcessorHardware) {
console.log('Attempting automatic reconnection for processor hardware...');
dispatch(wsReconnect());
} else if (closeEvent.code !== 4001 && closeEvent.code !== 4002) {
console.log(
'Attempting automatic reconnection after unexpected disconnect...'
);
dispatch(wsReconnect());
}
}, 5000);
```

## Testing Checklist

When testing this implementation, verify:

- [ ] Room status is requested when app starts and connects
- [ ] Room status is requested when room data/clientId becomes available
- [ ] Room status is requested when user navigates to a different room
- [ ] No duplicate room status requests (check network tab)
- [ ] Console logs show the expected flow
- [ ] Room state is updated correctly after status response
- [ ] **Automatic reconnection works when server is on processor hardware**
- [ ] **Manual reconnection required when server is NOT on processor hardware**
- [ ] **Unexpected disconnects trigger automatic reconnection**

## Debug Logging

All console logs include `[WebSocket Middleware]` prefix for easy filtering:

```javascript
// Filter console logs in browser DevTools:
console.log.filter = (msg) => msg.includes('[WebSocket Middleware]');
```

## Related Files

- `src/lib/store/middleware/websocketMiddleware.ts` - Main implementation
- `src/lib/store/runtimeConfig/runtimeConfig.slice.ts` - Actions we listen for
- `src/lib/utils/WebsocketProvider.tsx` - Component that uses the middleware
- `WEBSOCKET_REFACTOR_SUMMARY.md` - Overall WebSocket refactor documentation
Loading
Loading