fix: recover chat state after idle reconnects
Resyncs active sessions after hidden upstream stream reconnects Recovers orphaned streaming parts with active-session snapshots Adds coverage for event-stream reconnect behavior
This commit is contained in:
@@ -874,15 +874,20 @@ const childStoreHasSessionState = (
|
||||
|| Object.prototype.hasOwnProperty.call(state.session_status ?? {}, sessionID)
|
||||
}
|
||||
|
||||
const childStoreHasMessagePartState = (
|
||||
childStores: ChildStoreManager,
|
||||
directory: string,
|
||||
messageID: string,
|
||||
): boolean => {
|
||||
const childStoreHasMessagePartState = (
|
||||
childStores: ChildStoreManager,
|
||||
directory: string,
|
||||
messageID: string,
|
||||
): boolean => {
|
||||
const store = childStores.getChild(directory)
|
||||
if (!store) return false
|
||||
return Object.prototype.hasOwnProperty.call(store.getState().part, messageID)
|
||||
}
|
||||
return Object.prototype.hasOwnProperty.call(store.getState().part, messageID)
|
||||
}
|
||||
|
||||
const getActiveDirectoryFallback = (childStores: ChildStoreManager): string | null => {
|
||||
if (!_activeDirectory || !_activeSession) return null
|
||||
return childStores.getChild(_activeDirectory) ? _activeDirectory : null
|
||||
}
|
||||
|
||||
const resolveDirectoryFromRoutingIndex = (
|
||||
routingIndex: EventRoutingIndex,
|
||||
@@ -927,12 +932,21 @@ const resolveDirectoryFromRoutingIndex = (
|
||||
}
|
||||
|
||||
// Scan child stores for a store that has parts for this message
|
||||
for (const [dir, store] of childStores.children) {
|
||||
if (Object.prototype.hasOwnProperty.call(store.getState().part, messageID)) {
|
||||
return dir
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [dir, store] of childStores.children) {
|
||||
if (Object.prototype.hasOwnProperty.call(store.getState().part, messageID)) {
|
||||
return dir
|
||||
}
|
||||
}
|
||||
|
||||
// Some reconnect/idle gaps can deliver part events before the matching
|
||||
// message.updated event and without a sessionID. If the user is actively
|
||||
// viewing a session, route the orphaned part event there so the reducer can
|
||||
// trigger HTTP materialization instead of dropping it as a global event.
|
||||
const activeDirectory = getActiveDirectoryFallback(childStores)
|
||||
if (activeDirectory) {
|
||||
return activeDirectory
|
||||
}
|
||||
}
|
||||
|
||||
// Single-store fallback: if there's only one directory, use it
|
||||
if (
|
||||
@@ -946,8 +960,25 @@ const resolveDirectoryFromRoutingIndex = (
|
||||
}
|
||||
}
|
||||
|
||||
return normalizedDirectory
|
||||
}
|
||||
return normalizedDirectory
|
||||
}
|
||||
|
||||
const resolveMaterializationSessionID = (
|
||||
materializationSessionID: string | undefined,
|
||||
messageID: string | undefined,
|
||||
resolvedDirectory: string,
|
||||
routingIndex: EventRoutingIndex,
|
||||
): string | undefined => {
|
||||
if (materializationSessionID) return materializationSessionID
|
||||
if (messageID) {
|
||||
const indexedSessionID = routingIndex.messageSessionById.get(messageID)
|
||||
if (indexedSessionID) return indexedSessionID
|
||||
}
|
||||
if (resolvedDirectory && resolvedDirectory === _activeDirectory && _activeSession) {
|
||||
return _activeSession
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const updateRoutingIndexFromEvent = (
|
||||
routingIndex: EventRoutingIndex,
|
||||
@@ -1559,14 +1590,19 @@ function handleEvent(
|
||||
|
||||
}
|
||||
|
||||
// Snapshot materialization is driven by typed reducer outcomes, not by
|
||||
// inferring meaning from a generic false/no-change result.
|
||||
if (materializationResult) {
|
||||
const materializationSessionID = materializationResult.sessionID ?? getSessionIdFromPayload(payload) ?? undefined
|
||||
if (materializationSessionID) {
|
||||
enqueueSessionMaterialization(resolvedDirectory, materializationSessionID, childStores)
|
||||
}
|
||||
}
|
||||
// Snapshot materialization is driven by typed reducer outcomes, not by
|
||||
// inferring meaning from a generic false/no-change result.
|
||||
if (materializationResult) {
|
||||
const materializationSessionID = resolveMaterializationSessionID(
|
||||
materializationResult.sessionID ?? getSessionIdFromPayload(payload) ?? undefined,
|
||||
materializationResult.messageID ?? getMessageIdFromPayload(payload) ?? undefined,
|
||||
resolvedDirectory,
|
||||
routingIndex,
|
||||
)
|
||||
if (materializationSessionID) {
|
||||
enqueueSessionMaterialization(resolvedDirectory, materializationSessionID, childStores)
|
||||
}
|
||||
}
|
||||
|
||||
updateRoutingIndexFromEvent(routingIndex, resolvedDirectory, payload)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user