Live video on the AT Protocol

feat(frontend): Support multiple delegation records per moderator

Update frontend state management to handle multiple separate delegation
records per moderator, matching backend bug fix.

- Merge permissions from all delegation records in useCanModerate
- Fix WebSocket record matching using rkey + createdAt
- Skip expired delegations during permission merge

Related to #718

authored by charlebois.info and committed by

makeworld 42a68382 ef0b5dd0

+44 -19
+21 -6
js/components/src/livestream-store/websocket-consumer.tsx
··· 144 144 }; 145 145 } else { 146 146 // Handle new/updated permission: add or update in the list 147 - const newPerm = permRecord as PlaceStreamModerationPermission.Record; 148 - const existingIndex = state.moderationPermissions.findIndex( 149 - (p) => p.moderator === newPerm.moderator, 150 - ); 147 + // Use createdAt as a unique identifier since multiple records can exist for the same moderator 148 + // (e.g., one record with "ban" permission, another with "hide" permission) 149 + // Note: rkey would be ideal but isn't always present in the WebSocket message 150 + const newPerm = permRecord as PlaceStreamModerationPermission.Record & { 151 + rkey?: string; 152 + }; 153 + const existingIndex = state.moderationPermissions.findIndex((p) => { 154 + const pWithRkey = p as PlaceStreamModerationPermission.Record & { 155 + rkey?: string; 156 + }; 157 + // Prefer matching by rkey if available, fall back to createdAt 158 + if (newPerm.rkey && pWithRkey.rkey) { 159 + return pWithRkey.rkey === newPerm.rkey; 160 + } 161 + return ( 162 + p.moderator === newPerm.moderator && 163 + p.createdAt === newPerm.createdAt 164 + ); 165 + }); 151 166 152 167 let newPermissions: PlaceStreamModerationPermission.Record[]; 153 168 if (existingIndex >= 0) { 154 - // Update existing 169 + // Update existing record with same moderator AND createdAt 155 170 newPermissions = [...state.moderationPermissions]; 156 171 newPermissions[existingIndex] = newPerm; 157 172 } else { 158 - // Add new 173 + // Add new record (could be a new record for an existing moderator with different permissions) 159 174 newPermissions = [...state.moderationPermissions, newPerm]; 160 175 } 161 176
+23 -13
js/components/src/streamplace-store/moderation.tsx
··· 146 146 setModerationPermissions, 147 147 ]); 148 148 149 - const delegation = moderationPermissions.find( 149 + // Find ALL delegation records for this moderator and merge their permissions 150 + const delegations = moderationPermissions.filter( 150 151 (perm) => perm.moderator === userDID, 151 152 ); 152 153 153 - // Extract permissions from the delegation record 154 - const permissions: string[] = delegation 155 - ? (() => { 156 - // Check if delegation has expired 157 - if (delegation.expirationTime) { 158 - const expiration = new Date(delegation.expirationTime); 159 - if (new Date() > expiration) { 160 - return []; 161 - } 154 + // Merge permissions from all delegation records for this moderator 155 + const permissions: string[] = delegations.reduce( 156 + (acc: string[], delegation) => { 157 + // Check if delegation has expired 158 + if (delegation.expirationTime) { 159 + const expiration = new Date(delegation.expirationTime); 160 + if (new Date() > expiration) { 161 + return acc; // Skip expired delegations 162 162 } 163 - return delegation.permissions || []; 164 - })() 165 - : []; 163 + } 164 + 165 + // Add all permissions from this delegation, avoiding duplicates 166 + const delegationPerms = delegation.permissions || []; 167 + for (const perm of delegationPerms) { 168 + if (!acc.includes(perm)) { 169 + acc.push(perm); 170 + } 171 + } 172 + return acc; 173 + }, 174 + [], 175 + ); 166 176 167 177 return { 168 178 canBan: isOwner || permissions.includes("ban"),