fix(sync): keep session renames stable (#2043)
* fix(sync): keep session renames stable * fix(sync): clarify rename mirror flow * fix(sync): clarify archive comment --------- Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
bashrusakh
Bohdan Triapitsyn
parent
9bfc5bf0be
commit
d5745aaac9
@@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import type { Session } from "@opencode-ai/sdk/v2"
|
||||||
import type { Event, Part, PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/sdk/v2/client"
|
import type { Event, Part, PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/sdk/v2/client"
|
||||||
import { applyDirectoryEvent } from "../event-reducer"
|
import { applyDirectoryEvent } from "../event-reducer"
|
||||||
import { INITIAL_STATE, type State } from "../types"
|
import { INITIAL_STATE, type State } from "../types"
|
||||||
@@ -55,6 +56,14 @@ function topLevelSessionOnlyPartUpdatedEvent(): Event {
|
|||||||
} as Event
|
} as Event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSession(title: string, time: Session["time"]): Session {
|
||||||
|
return {
|
||||||
|
id: "ses_1",
|
||||||
|
title,
|
||||||
|
time,
|
||||||
|
} as Session
|
||||||
|
}
|
||||||
|
|
||||||
describe("applyDirectoryEvent", () => {
|
describe("applyDirectoryEvent", () => {
|
||||||
test("returns typed materialization when delta arrives before parts", () => {
|
test("returns typed materialization when delta arrives before parts", () => {
|
||||||
const result = applyDirectoryEvent(state(), deltaEvent())
|
const result = applyDirectoryEvent(state(), deltaEvent())
|
||||||
@@ -129,6 +138,20 @@ describe("applyDirectoryEvent", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("skips stale session.updated events so a newer title survives", () => {
|
||||||
|
const draft = state({ session: [buildSession("New Title", { created: 1, updated: 20 })] })
|
||||||
|
|
||||||
|
const result = applyDirectoryEvent(draft, {
|
||||||
|
type: "session.updated",
|
||||||
|
properties: {
|
||||||
|
info: buildSession("Old Title", { created: 1, updated: 10 }),
|
||||||
|
},
|
||||||
|
} as Event)
|
||||||
|
|
||||||
|
expect(result).toBe(false)
|
||||||
|
expect(draft.session[0]?.title).toBe("New Title")
|
||||||
|
})
|
||||||
|
|
||||||
test("applies part update without materialization when owning message exists", () => {
|
test("applies part update without materialization when owning message exists", () => {
|
||||||
const draft = state({
|
const draft = state({
|
||||||
message: { ses_1: [{ id: "msg_1", sessionID: "ses_1", role: "assistant", time: { created: 1 } } as never] },
|
message: { ses_1: [{ id: "msg_1", sessionID: "ses_1", role: "assistant", time: { created: 1 } } as never] },
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import type { Session } from "@opencode-ai/sdk/v2"
|
||||||
|
|
||||||
|
import { shouldSkipStaleSessionEvent } from "../session-event-freshness"
|
||||||
|
|
||||||
|
const buildSession = (title: string, time: Partial<NonNullable<Session["time"]>>): Session => ({
|
||||||
|
id: "ses_1",
|
||||||
|
title,
|
||||||
|
time: time as Session["time"],
|
||||||
|
} as Session)
|
||||||
|
|
||||||
|
describe("shouldSkipStaleSessionEvent", () => {
|
||||||
|
test("skips a stale SSE session update after a newer local rename", () => {
|
||||||
|
const current = buildSession("New Title", { created: 1, updated: 20 })
|
||||||
|
const incoming = buildSession("Old Title", { created: 1, updated: 10 })
|
||||||
|
|
||||||
|
expect(shouldSkipStaleSessionEvent(current, incoming)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("allows a fresher SSE update to apply", () => {
|
||||||
|
const current = buildSession("Old Title", { created: 1, updated: 10 })
|
||||||
|
const incoming = buildSession("New Title", { created: 1, updated: 20 })
|
||||||
|
|
||||||
|
expect(shouldSkipStaleSessionEvent(current, incoming)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to created timestamp when updated is missing", () => {
|
||||||
|
const current = buildSession("Current", { created: 20 })
|
||||||
|
const incoming = buildSession("Incoming", { created: 10 })
|
||||||
|
|
||||||
|
expect(shouldSkipStaleSessionEvent(current, incoming)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { beforeEach, describe, expect, mock, test } from "bun:test"
|
||||||
|
import type { Event, Session } from "@opencode-ai/sdk/v2/client"
|
||||||
|
|
||||||
|
let currentSessions: Session[] = []
|
||||||
|
const upsertedSessions: Session[] = []
|
||||||
|
|
||||||
|
mock.module("@/stores/useGlobalSessionsStore", () => ({
|
||||||
|
useGlobalSessionsStore: {
|
||||||
|
getState: () => ({
|
||||||
|
activeSessions: currentSessions,
|
||||||
|
archivedSessions: [] as Session[],
|
||||||
|
upsertSession: (session: Session) => {
|
||||||
|
upsertedSessions.push(session)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
import { applySessionEventToGlobalSessions } from "../session-event-router"
|
||||||
|
|
||||||
|
const buildSession = (title: string, time: Session["time"]): Session => ({
|
||||||
|
id: "ses_1",
|
||||||
|
title,
|
||||||
|
time,
|
||||||
|
} as Session)
|
||||||
|
|
||||||
|
const buildEvent = (session: Session): Event => ({
|
||||||
|
type: "session.updated",
|
||||||
|
properties: {
|
||||||
|
info: session,
|
||||||
|
},
|
||||||
|
} as Event)
|
||||||
|
|
||||||
|
describe("applySessionEventToGlobalSessions", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
currentSessions = []
|
||||||
|
upsertedSessions.length = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
test("skips stale global session.updated echoes after a newer rename", () => {
|
||||||
|
currentSessions = [buildSession("New Title", { created: 1, updated: 20 })]
|
||||||
|
|
||||||
|
applySessionEventToGlobalSessions(buildEvent(buildSession("Old Title", { created: 1, updated: 10 })))
|
||||||
|
|
||||||
|
expect(upsertedSessions).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -14,6 +14,7 @@ import type { FileDiff, GlobalState, State } from "./types"
|
|||||||
import { dropSessionCaches } from "./session-cache"
|
import { dropSessionCaches } from "./session-cache"
|
||||||
import { stripSessionDiffSnapshots } from "./sanitize"
|
import { stripSessionDiffSnapshots } from "./sanitize"
|
||||||
import { syncDebug } from "./debug"
|
import { syncDebug } from "./debug"
|
||||||
|
import { shouldSkipStaleSessionEvent } from "./session-event-freshness"
|
||||||
|
|
||||||
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
||||||
const DELTA_OVERLAP_FIELDS = ["text", "output"] as const
|
const DELTA_OVERLAP_FIELDS = ["text", "output"] as const
|
||||||
@@ -226,6 +227,9 @@ export function applyDirectoryEvent(
|
|||||||
const info = stripSessionDiffSnapshots((event.properties as { info: Session }).info)
|
const info = stripSessionDiffSnapshots((event.properties as { info: Session }).info)
|
||||||
const sessions = draft.session
|
const sessions = draft.session
|
||||||
const result = Binary.search(sessions, info.id, (s) => s.id)
|
const result = Binary.search(sessions, info.id, (s) => s.id)
|
||||||
|
if (result.found && shouldSkipStaleSessionEvent(sessions[result.index], info)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (result.found) {
|
if (result.found) {
|
||||||
sessions[result.index] = info
|
sessions[result.index] = info
|
||||||
} else {
|
} else {
|
||||||
@@ -240,6 +244,13 @@ export function applyDirectoryEvent(
|
|||||||
const info = stripSessionDiffSnapshots((event.properties as { info: Session }).info)
|
const info = stripSessionDiffSnapshots((event.properties as { info: Session }).info)
|
||||||
const sessions = draft.session
|
const sessions = draft.session
|
||||||
const result = Binary.search(sessions, info.id, (s) => s.id)
|
const result = Binary.search(sessions, info.id, (s) => s.id)
|
||||||
|
// Keep the freshness check ahead of the archive branch: direct archive
|
||||||
|
// responses handle the store update on their own (optimistic removal +
|
||||||
|
// SDK response), so stale SSE echoes should not win just because they
|
||||||
|
// mark the session archived.
|
||||||
|
if (result.found && shouldSkipStaleSessionEvent(sessions[result.index], info)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (info.time.archived) {
|
if (info.time.archived) {
|
||||||
if (result.found) sessions.splice(result.index, 1)
|
if (result.found) sessions.splice(result.index, 1)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ let sessionRevertResult: { data?: unknown; error?: unknown; response?: { status?
|
|||||||
let questionReplyError: unknown | null = null
|
let questionReplyError: unknown | null = null
|
||||||
let questionRejectError: unknown | null = null
|
let questionRejectError: unknown | null = null
|
||||||
let sessionShareResult: { data?: unknown; error?: unknown; response?: { status?: number } } = {}
|
let sessionShareResult: { data?: unknown; error?: unknown; response?: { status?: number } } = {}
|
||||||
|
let sessionUpdateResult: { data?: unknown; error?: unknown; response?: { status?: number } } = {}
|
||||||
let sessionMessagesResult: { data?: unknown; error?: unknown; response?: { status?: number } } = { data: [] }
|
let sessionMessagesResult: { data?: unknown; error?: unknown; response?: { status?: number } } = { data: [] }
|
||||||
const globalUpsertedSessions: unknown[] = []
|
const globalUpsertedSessions: unknown[] = []
|
||||||
|
|
||||||
@@ -52,6 +53,14 @@ const mockSdk = {
|
|||||||
replyCalls.push({ method: "session.abort", params })
|
replyCalls.push({ method: "session.abort", params })
|
||||||
return Promise.resolve({ data: true })
|
return Promise.resolve({ data: true })
|
||||||
}),
|
}),
|
||||||
|
updateSession: mock((sessionId: string, changes: Record<string, unknown>, directory?: string | null) => {
|
||||||
|
replyCalls.push({ method: "session.update", params: { sessionID: sessionId, ...changes, directory } })
|
||||||
|
return Promise.resolve(sessionUpdateResult.data as Session)
|
||||||
|
}),
|
||||||
|
update: mock((params: Record<string, unknown>) => {
|
||||||
|
replyCalls.push({ method: "session.update", params })
|
||||||
|
return Promise.resolve(sessionUpdateResult)
|
||||||
|
}),
|
||||||
share: mock((params: Record<string, unknown>) => {
|
share: mock((params: Record<string, unknown>) => {
|
||||||
replyCalls.push({ method: "session.share", params })
|
replyCalls.push({ method: "session.share", params })
|
||||||
return Promise.resolve(sessionShareResult)
|
return Promise.resolve(sessionShareResult)
|
||||||
@@ -112,6 +121,10 @@ mock.module("@/lib/opencode/client", () => ({
|
|||||||
}
|
}
|
||||||
return Promise.resolve(sessionRevertResult.data)
|
return Promise.resolve(sessionRevertResult.data)
|
||||||
}),
|
}),
|
||||||
|
updateSession: mock((sessionId: string, changes: Record<string, unknown>, directory?: string | null) => {
|
||||||
|
replyCalls.push({ method: "session.update", params: { sessionID: sessionId, ...changes, directory } })
|
||||||
|
return Promise.resolve(sessionUpdateResult.data)
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -340,6 +353,34 @@ describe("shareSession live state", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("updateSessionTitle live state", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
replyCalls.length = 0
|
||||||
|
globalUpsertedSessions.length = 0
|
||||||
|
sessionUpdateResult = {}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("updates the live directory store after renaming", async () => {
|
||||||
|
const oldSession = { id: "session-a", title: "Old Title", time: { created: 1, updated: 1 } } as Session
|
||||||
|
const updatedSession = { id: "session-a", title: "New Title", time: { created: 1, updated: 2 } } as Session
|
||||||
|
const sessionStore = createStore({}, { session: [oldSession] })
|
||||||
|
const childStores = createChildStores([["/test/project", sessionStore]])
|
||||||
|
sessionUpdateResult = { data: updatedSession }
|
||||||
|
|
||||||
|
const { setActionRefs, updateSessionTitle } = await import("./session-actions")
|
||||||
|
setActionRefs(mockSdk as unknown as OpencodeClient, childStores, () => "/current/project")
|
||||||
|
|
||||||
|
await updateSessionTitle("session-a", "New Title")
|
||||||
|
|
||||||
|
const updateCall = replyCalls.find((call) => call.method === "session.update")
|
||||||
|
expect(updateCall?.params.sessionID).toBe("session-a")
|
||||||
|
expect(updateCall?.params.title).toBe("New Title")
|
||||||
|
expect(updateCall?.params.directory).toBe("/test/project")
|
||||||
|
expect(globalUpsertedSessions).toEqual([updatedSession])
|
||||||
|
expect(sessionStore.getState().session[0].title).toBe("New Title")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("optimisticSend target directory", () => {
|
describe("optimisticSend target directory", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
replyCalls.length = 0
|
replyCalls.length = 0
|
||||||
|
|||||||
@@ -161,9 +161,9 @@ export function getSessionLastAssistantModel(sessionId: string): { providerID: s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLiveSession(session: Session, directory?: string): void {
|
function updateLiveSession(session: Session, directory?: string): boolean {
|
||||||
const stores = _childStores
|
const stores = _childStores
|
||||||
if (!stores) return
|
if (!stores) return false
|
||||||
|
|
||||||
const candidates = directory
|
const candidates = directory
|
||||||
? [[directory, stores.getChild(directory)] as const]
|
? [[directory, stores.getChild(directory)] as const]
|
||||||
@@ -178,8 +178,17 @@ function updateLiveSession(session: Session, directory?: string): void {
|
|||||||
const next = [...current]
|
const next = [...current]
|
||||||
next[index] = mergeSessionDirectoryMetadata(session, current[index])
|
next[index] = mergeSessionDirectoryMetadata(session, current[index])
|
||||||
store.setState({ session: next })
|
store.setState({ session: next })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mirrorSessionIntoLiveStores(session: Session, directory?: string): void {
|
||||||
|
if (directory && updateLiveSession(session, directory)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
updateLiveSession(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
function dir() {
|
function dir() {
|
||||||
@@ -624,6 +633,7 @@ export async function updateSessionTitle(sessionId: string, title: string): Prom
|
|||||||
const sessionDirectory = getSessionDirectory(sessionId)
|
const sessionDirectory = getSessionDirectory(sessionId)
|
||||||
const session = await opencodeClient.updateSession(sessionId, { title }, sessionDirectory)
|
const session = await opencodeClient.updateSession(sessionId, { title }, sessionDirectory)
|
||||||
useGlobalSessionsStore.getState().upsertSession(session)
|
useGlobalSessionsStore.getState().upsertSession(session)
|
||||||
|
mirrorSessionIntoLiveStores(session, sessionDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function shareSession(sessionId: string): Promise<Session | null> {
|
export async function shareSession(sessionId: string): Promise<Session | null> {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Session } from "@opencode-ai/sdk/v2"
|
||||||
|
|
||||||
|
const getSessionRecencyTimestamp = (session: Session): number => {
|
||||||
|
const updatedAt = session.time?.updated
|
||||||
|
if (typeof updatedAt === "number" && Number.isFinite(updatedAt)) {
|
||||||
|
return updatedAt
|
||||||
|
}
|
||||||
|
const createdAt = session.time?.created
|
||||||
|
return typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export const shouldSkipStaleSessionEvent = (currentSession: Session | null, incomingSession: Session): boolean => {
|
||||||
|
if (!currentSession) return false
|
||||||
|
return getSessionRecencyTimestamp(incomingSession) < getSessionRecencyTimestamp(currentSession)
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { Event, Session } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { useGlobalSessionsStore } from "@/stores/useGlobalSessionsStore"
|
||||||
|
import { stripSessionDiffSnapshots } from "./sanitize"
|
||||||
|
import { shouldSkipStaleSessionEvent } from "./session-event-freshness"
|
||||||
|
|
||||||
|
const getSessionInfoFromPayload = (event: Event): Session | null => {
|
||||||
|
if (event.type !== "session.created" && event.type !== "session.updated" && event.type !== "session.deleted") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const properties = (event as { properties?: unknown }).properties
|
||||||
|
if (!properties || typeof properties !== "object") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = (properties as { info?: unknown }).info
|
||||||
|
if (!info || typeof info !== "object") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = info as Partial<Session>
|
||||||
|
if (typeof session.id !== "string" || !session.time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return stripSessionDiffSnapshots(session as Session)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGlobalSessionSnapshot = (sessionId: string): Session | null => {
|
||||||
|
const global = useGlobalSessionsStore.getState()
|
||||||
|
return [...global.activeSessions, ...global.archivedSessions].find((session) => session.id === sessionId) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const applySessionEventToGlobalSessions = (payload: Event): void => {
|
||||||
|
if (payload.type === "session.created" || payload.type === "session.updated") {
|
||||||
|
const session = getSessionInfoFromPayload(payload)
|
||||||
|
if (session) {
|
||||||
|
const currentSession = getGlobalSessionSnapshot(session.id)
|
||||||
|
if (!shouldSkipStaleSessionEvent(currentSession, session)) {
|
||||||
|
useGlobalSessionsStore.getState().upsertSession(session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type === "session.deleted") {
|
||||||
|
const sessionID = (payload as { properties?: { sessionID?: string } }).properties?.sessionID ?? getSessionInfoFromPayload(payload)?.id
|
||||||
|
if (sessionID) {
|
||||||
|
useGlobalSessionsStore.getState().removeSessions([sessionID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import { updateStreamingState } from "./streaming"
|
|||||||
import { setActionRefs } from "./session-actions"
|
import { setActionRefs } from "./session-actions"
|
||||||
import { setSyncRefs, getAllSyncSessions } from "./sync-refs"
|
import { setSyncRefs, getAllSyncSessions } from "./sync-refs"
|
||||||
import { stripMessageDiffSnapshots, stripSessionDiffSnapshots } from "./sanitize"
|
import { stripMessageDiffSnapshots, stripSessionDiffSnapshots } from "./sanitize"
|
||||||
|
import { applySessionEventToGlobalSessions } from "./session-event-router"
|
||||||
import { syncDebug } from "./debug"
|
import { syncDebug } from "./debug"
|
||||||
import { getReconnectCandidateSessionIds } from "./reconnect-recovery"
|
import { getReconnectCandidateSessionIds } from "./reconnect-recovery"
|
||||||
import { opencodeClient } from "@/lib/opencode/client"
|
import { opencodeClient } from "@/lib/opencode/client"
|
||||||
@@ -46,7 +47,6 @@ import { getRuntimeKey } from "@/lib/runtime-switch"
|
|||||||
import { getRegisteredRuntimeAPIs } from "@/contexts/runtimeAPIRegistry"
|
import { getRegisteredRuntimeAPIs } from "@/contexts/runtimeAPIRegistry"
|
||||||
import { setSessionPrefetch } from "./session-prefetch-cache"
|
import { setSessionPrefetch } from "./session-prefetch-cache"
|
||||||
import { listGlobalSessionPages } from "@/stores/globalSessions"
|
import { listGlobalSessionPages } from "@/stores/globalSessions"
|
||||||
import { useGlobalSessionsStore } from "@/stores/useGlobalSessionsStore"
|
|
||||||
import { areRequestArraysReferentiallyEqual, collectScopedBlockingRequests } from "./scoped-blocking-requests"
|
import { areRequestArraysReferentiallyEqual, collectScopedBlockingRequests } from "./scoped-blocking-requests"
|
||||||
import { EMPTY_USER_MESSAGE_HISTORY_SNAPSHOT, buildUserMessageHistorySnapshot, type UserMessageHistorySnapshot } from "./user-message-history"
|
import { EMPTY_USER_MESSAGE_HISTORY_SNAPSHOT, buildUserMessageHistorySnapshot, type UserMessageHistorySnapshot } from "./user-message-history"
|
||||||
import { runtimeFetch } from "@/lib/runtime-fetch"
|
import { runtimeFetch } from "@/lib/runtime-fetch"
|
||||||
@@ -659,46 +659,6 @@ const getSessionIdFromPayload = (event: Event): string | null => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSessionInfoFromPayload = (event: Event): Session | null => {
|
|
||||||
if (event.type !== "session.created" && event.type !== "session.updated" && event.type !== "session.deleted") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const properties = (event as { properties?: unknown }).properties
|
|
||||||
if (!properties || typeof properties !== "object") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = (properties as { info?: unknown }).info
|
|
||||||
if (!info || typeof info !== "object") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = info as Partial<Session>
|
|
||||||
if (typeof session.id !== "string" || !session.time) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return stripSessionDiffSnapshots(session as Session)
|
|
||||||
}
|
|
||||||
|
|
||||||
const applySessionEventToGlobalSessions = (payload: Event) => {
|
|
||||||
if (payload.type === "session.created" || payload.type === "session.updated") {
|
|
||||||
const session = getSessionInfoFromPayload(payload)
|
|
||||||
if (session) {
|
|
||||||
useGlobalSessionsStore.getState().upsertSession(session)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payload.type === "session.deleted") {
|
|
||||||
const sessionID = getSessionIdFromPayload(payload) ?? getSessionInfoFromPayload(payload)?.id
|
|
||||||
if (sessionID) {
|
|
||||||
useGlobalSessionsStore.getState().removeSessions([sessionID])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMessageIdFromPayload = (event: Event): string | null => {
|
const getMessageIdFromPayload = (event: Event): string | null => {
|
||||||
const properties = (event as { properties?: unknown }).properties
|
const properties = (event as { properties?: unknown }).properties
|
||||||
if (!properties || typeof properties !== "object") {
|
if (!properties || typeof properties !== "object") {
|
||||||
|
|||||||
Reference in New Issue
Block a user