mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-15 03:01:58 +08:00
fix(sessions): canonicalize sidebar ids and timestamps
The sidebar could keep a provider-native id after backend remapping. That left a duplicate non-working session visible until refresh. Fresh sessions could also appear hours old. SQLite CURRENT_TIMESTAMP is UTC without a timezone suffix. Browser parsing then treated those values like local time. Broadcast a canonical session_upserted event when the provider id is mapped. Collapse provider-id aliases onto the stable app session id in the client. Normalize session-row timestamps to ISO UTC when reading from the repository.
This commit is contained in:
@@ -70,3 +70,15 @@ test('createSession reactivates archived rows when the session becomes active ag
|
|||||||
assert.equal(restoredSession?.isArchived, 0);
|
assert.equal(restoredSession?.isArchived, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('repository reads normalize SQLite UTC timestamps to ISO strings', async () => {
|
||||||
|
await withIsolatedDatabase(() => {
|
||||||
|
sessionsDb.createAppSession('session-timezone', 'claude', '/workspace/demo-project');
|
||||||
|
|
||||||
|
const row = sessionsDb.getSessionById('session-timezone');
|
||||||
|
assert.ok(row?.created_at.endsWith('Z'));
|
||||||
|
assert.ok(row?.updated_at.endsWith('Z'));
|
||||||
|
assert.match(row?.created_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
|
||||||
|
assert.match(row?.updated_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -17,10 +17,19 @@ type SessionRow = {
|
|||||||
const SESSION_ROW_COLUMNS =
|
const SESSION_ROW_COLUMNS =
|
||||||
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at';
|
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at';
|
||||||
|
|
||||||
|
const SQLITE_UTC_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
||||||
|
|
||||||
function normalizeTimestamp(value?: string): string | null {
|
function normalizeTimestamp(value?: string): string | null {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
|
|
||||||
const parsed = new Date(value);
|
// SQLite CURRENT_TIMESTAMP is stored as UTC without a timezone suffix.
|
||||||
|
// Normalize it here so every session reader returns canonical ISO strings
|
||||||
|
// and the sidebar never interprets fresh rows as local-time "hours old".
|
||||||
|
const normalizedValue = SQLITE_UTC_TIMESTAMP_REGEX.test(value)
|
||||||
|
? `${value.replace(' ', 'T')}Z`
|
||||||
|
: value;
|
||||||
|
|
||||||
|
const parsed = new Date(normalizedValue);
|
||||||
if (Number.isNaN(parsed.getTime())) {
|
if (Number.isNaN(parsed.getTime())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -28,6 +37,22 @@ function normalizeTimestamp(value?: string): string | null {
|
|||||||
return parsed.toISOString();
|
return parsed.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSessionRow<T extends SessionRow | null | undefined>(row: T): T {
|
||||||
|
if (!row) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
created_at: normalizeTimestamp(row.created_at) ?? row.created_at,
|
||||||
|
updated_at: normalizeTimestamp(row.updated_at) ?? row.updated_at,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSessionRows(rows: SessionRow[]): SessionRow[] {
|
||||||
|
return rows.map((row) => normalizeSessionRow(row) as SessionRow);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeProjectPathForProvider(provider: string, projectPath: string): string {
|
function normalizeProjectPathForProvider(provider: string, projectPath: string): string {
|
||||||
void provider;
|
void provider;
|
||||||
return normalizeProjectPath(projectPath);
|
return normalizeProjectPath(projectPath);
|
||||||
@@ -207,7 +232,7 @@ export const sessionsDb = {
|
|||||||
)
|
)
|
||||||
.get(sessionId) as SessionRow | undefined;
|
.get(sessionId) as SessionRow | undefined;
|
||||||
|
|
||||||
return row ?? null;
|
return normalizeSessionRow(row) ?? null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -229,18 +254,20 @@ export const sessionsDb = {
|
|||||||
)
|
)
|
||||||
.get(providerSessionId) as SessionRow | undefined;
|
.get(providerSessionId) as SessionRow | undefined;
|
||||||
|
|
||||||
return row ?? null;
|
return normalizeSessionRow(row) ?? null;
|
||||||
},
|
},
|
||||||
|
|
||||||
getAllSessions(): SessionRow[] {
|
getAllSessions(): SessionRow[] {
|
||||||
const db = getConnection();
|
const db = getConnection();
|
||||||
return db
|
const rows = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SESSION_ROW_COLUMNS}
|
`SELECT ${SESSION_ROW_COLUMNS}
|
||||||
FROM sessions
|
FROM sessions
|
||||||
WHERE isArchived = 0`
|
WHERE isArchived = 0`
|
||||||
)
|
)
|
||||||
.all() as SessionRow[];
|
.all() as SessionRow[];
|
||||||
|
|
||||||
|
return normalizeSessionRows(rows);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -249,7 +276,7 @@ export const sessionsDb = {
|
|||||||
*/
|
*/
|
||||||
getArchivedSessions(): SessionRow[] {
|
getArchivedSessions(): SessionRow[] {
|
||||||
const db = getConnection();
|
const db = getConnection();
|
||||||
return db
|
const rows = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SESSION_ROW_COLUMNS}
|
`SELECT ${SESSION_ROW_COLUMNS}
|
||||||
FROM sessions
|
FROM sessions
|
||||||
@@ -257,12 +284,14 @@ export const sessionsDb = {
|
|||||||
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC`
|
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC`
|
||||||
)
|
)
|
||||||
.all() as SessionRow[];
|
.all() as SessionRow[];
|
||||||
|
|
||||||
|
return normalizeSessionRows(rows);
|
||||||
},
|
},
|
||||||
|
|
||||||
getSessionsByProjectPath(projectPath: string): SessionRow[] {
|
getSessionsByProjectPath(projectPath: string): SessionRow[] {
|
||||||
const db = getConnection();
|
const db = getConnection();
|
||||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||||
return db
|
const rows = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SESSION_ROW_COLUMNS}
|
`SELECT ${SESSION_ROW_COLUMNS}
|
||||||
FROM sessions
|
FROM sessions
|
||||||
@@ -270,6 +299,8 @@ export const sessionsDb = {
|
|||||||
AND isArchived = 0`
|
AND isArchived = 0`
|
||||||
)
|
)
|
||||||
.all(normalizedProjectPath) as SessionRow[];
|
.all(normalizedProjectPath) as SessionRow[];
|
||||||
|
|
||||||
|
return normalizeSessionRows(rows);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -279,19 +310,21 @@ export const sessionsDb = {
|
|||||||
getSessionsByProjectPathIncludingArchived(projectPath: string): SessionRow[] {
|
getSessionsByProjectPathIncludingArchived(projectPath: string): SessionRow[] {
|
||||||
const db = getConnection();
|
const db = getConnection();
|
||||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||||
return db
|
const rows = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SESSION_ROW_COLUMNS}
|
`SELECT ${SESSION_ROW_COLUMNS}
|
||||||
FROM sessions
|
FROM sessions
|
||||||
WHERE project_path = ?`
|
WHERE project_path = ?`
|
||||||
)
|
)
|
||||||
.all(normalizedProjectPath) as SessionRow[];
|
.all(normalizedProjectPath) as SessionRow[];
|
||||||
|
|
||||||
|
return normalizeSessionRows(rows);
|
||||||
},
|
},
|
||||||
|
|
||||||
getSessionsByProjectPathPage(projectPath: string, limit: number, offset: number): SessionRow[] {
|
getSessionsByProjectPathPage(projectPath: string, limit: number, offset: number): SessionRow[] {
|
||||||
const db = getConnection();
|
const db = getConnection();
|
||||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||||
return db
|
const rows = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SESSION_ROW_COLUMNS}
|
`SELECT ${SESSION_ROW_COLUMNS}
|
||||||
FROM sessions
|
FROM sessions
|
||||||
@@ -301,6 +334,8 @@ export const sessionsDb = {
|
|||||||
LIMIT ? OFFSET ?`
|
LIMIT ? OFFSET ?`
|
||||||
)
|
)
|
||||||
.all(normalizedProjectPath, limit, offset) as SessionRow[];
|
.all(normalizedProjectPath, limit, offset) as SessionRow[];
|
||||||
|
|
||||||
|
return normalizeSessionRows(rows);
|
||||||
},
|
},
|
||||||
|
|
||||||
countSessionsByProjectPath(projectPath: string): number {
|
countSessionsByProjectPath(projectPath: string): number {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { sessionsDb } from '@/modules/database/index.js';
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { projectsDb, sessionsDb } from '@/modules/database/index.js';
|
||||||
|
import { generateDisplayName } from '@/modules/projects/index.js';
|
||||||
import { ChatSessionWriter } from '@/modules/websocket/services/chat-session-writer.service.js';
|
import { ChatSessionWriter } from '@/modules/websocket/services/chat-session-writer.service.js';
|
||||||
|
import { connectedClients, WS_OPEN_STATE } from '@/modules/websocket/services/websocket-state.service.js';
|
||||||
import type {
|
import type {
|
||||||
LLMProvider,
|
LLMProvider,
|
||||||
NormalizedMessage,
|
NormalizedMessage,
|
||||||
@@ -58,6 +62,48 @@ const MAX_BUFFERED_EVENTS_PER_RUN = 5000;
|
|||||||
*/
|
*/
|
||||||
const runs = new Map<string, ChatRun>();
|
const runs = new Map<string, ChatRun>();
|
||||||
|
|
||||||
|
async function broadcastCanonicalSessionUpsert(appSessionId: string): Promise<void> {
|
||||||
|
const row = sessionsDb.getSessionById(appSessionId);
|
||||||
|
if (!row || row.isArchived) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectPath = row.project_path;
|
||||||
|
const project = projectPath ? projectsDb.getProjectPath(projectPath) : null;
|
||||||
|
const displayName = project?.custom_project_name?.trim()
|
||||||
|
? project.custom_project_name
|
||||||
|
: await generateDisplayName(path.basename(projectPath ?? '') || (projectPath ?? ''), projectPath);
|
||||||
|
|
||||||
|
const payload = JSON.stringify({
|
||||||
|
kind: 'session_upserted',
|
||||||
|
sessionId: row.session_id,
|
||||||
|
providerSessionId: row.provider_session_id,
|
||||||
|
provider: row.provider,
|
||||||
|
session: {
|
||||||
|
id: row.session_id,
|
||||||
|
summary: row.custom_name || '',
|
||||||
|
messageCount: 0,
|
||||||
|
lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
|
||||||
|
},
|
||||||
|
project: project
|
||||||
|
? {
|
||||||
|
projectId: project.project_id,
|
||||||
|
path: project.project_path,
|
||||||
|
fullPath: project.project_path,
|
||||||
|
displayName,
|
||||||
|
isStarred: Boolean(project.isStarred),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
connectedClients.forEach((client) => {
|
||||||
|
if (client.readyState === WS_OPEN_STATE) {
|
||||||
|
client.send(payload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function evictRunLater(appSessionId: string): void {
|
function evictRunLater(appSessionId: string): void {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
const run = runs.get(appSessionId);
|
const run = runs.get(appSessionId);
|
||||||
@@ -132,6 +178,14 @@ function recordProviderSessionId(run: ChatRun, providerSessionId: string): void
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
sessionsDb.assignProviderSessionId(run.appSessionId, providerSessionId);
|
sessionsDb.assignProviderSessionId(run.appSessionId, providerSessionId);
|
||||||
|
void broadcastCanonicalSessionUpsert(run.appSessionId).catch((error) => {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error('[ChatRunRegistry] Failed to broadcast canonical session mapping', {
|
||||||
|
appSessionId: run.appSessionId,
|
||||||
|
providerSessionId,
|
||||||
|
error: message,
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
console.error('[ChatRunRegistry] Failed to persist provider session id mapping', {
|
console.error('[ChatRunRegistry] Failed to persist provider session id mapping', {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import test from 'node:test';
|
|||||||
|
|
||||||
import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
|
import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
|
||||||
import { chatRunRegistry } from '@/modules/websocket/services/chat-run-registry.service.js';
|
import { chatRunRegistry } from '@/modules/websocket/services/chat-run-registry.service.js';
|
||||||
import type { NormalizedMessage } from '@/shared/types.js';
|
import { connectedClients } from '@/modules/websocket/services/websocket-state.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal stand-in for a websocket connection: collects every JSON frame the
|
* Minimal stand-in for a websocket connection: collects every JSON frame the
|
||||||
@@ -14,10 +14,10 @@ import type { NormalizedMessage } from '@/shared/types.js';
|
|||||||
*/
|
*/
|
||||||
class FakeConnection {
|
class FakeConnection {
|
||||||
readyState = 1; // WS_OPEN_STATE
|
readyState = 1; // WS_OPEN_STATE
|
||||||
frames: NormalizedMessage[] = [];
|
frames: Array<Record<string, unknown>> = [];
|
||||||
|
|
||||||
send(data: string): void {
|
send(data: string): void {
|
||||||
this.frames.push(JSON.parse(data) as NormalizedMessage);
|
this.frames.push(JSON.parse(data) as Record<string, unknown>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +33,7 @@ async function withIsolatedDatabase(runTest: () => void | Promise<void>): Promis
|
|||||||
try {
|
try {
|
||||||
await runTest();
|
await runTest();
|
||||||
} finally {
|
} finally {
|
||||||
|
connectedClients.clear();
|
||||||
chatRunRegistry.clearAll();
|
chatRunRegistry.clearAll();
|
||||||
closeConnection();
|
closeConnection();
|
||||||
if (previousDatabasePath === undefined) {
|
if (previousDatabasePath === undefined) {
|
||||||
@@ -72,6 +73,7 @@ test('session_created is swallowed and persisted as the provider-id mapping', as
|
|||||||
await withIsolatedDatabase(() => {
|
await withIsolatedDatabase(() => {
|
||||||
sessionsDb.createAppSession('app-run-2', 'cursor', '/workspace/demo');
|
sessionsDb.createAppSession('app-run-2', 'cursor', '/workspace/demo');
|
||||||
const connection = new FakeConnection();
|
const connection = new FakeConnection();
|
||||||
|
connectedClients.add(connection as never);
|
||||||
const run = chatRunRegistry.startRun({
|
const run = chatRunRegistry.startRun({
|
||||||
appSessionId: 'app-run-2',
|
appSessionId: 'app-run-2',
|
||||||
provider: 'cursor',
|
provider: 'cursor',
|
||||||
@@ -88,9 +90,12 @@ test('session_created is swallowed and persisted as the provider-id mapping', as
|
|||||||
newSessionId: 'cursor-native-7',
|
newSessionId: 'cursor-native-7',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Never forwarded to the client...
|
// The provider-native event itself is never forwarded...
|
||||||
assert.equal(connection.frames.length, 0);
|
const sessionUpserts = connection.frames.filter((frame) => frame.kind === 'session_upserted');
|
||||||
// ...but recorded in the registry and persisted in the database.
|
assert.equal(sessionUpserts.length, 1);
|
||||||
|
assert.equal(sessionUpserts[0]?.sessionId, 'app-run-2');
|
||||||
|
assert.equal(sessionUpserts[0]?.providerSessionId, 'cursor-native-7');
|
||||||
|
// ...but the canonical mapping is recorded and persisted in the database.
|
||||||
assert.equal(run.providerSessionId, 'cursor-native-7');
|
assert.equal(run.providerSessionId, 'cursor-native-7');
|
||||||
assert.equal(sessionsDb.getSessionById('app-run-2')?.provider_session_id, 'cursor-native-7');
|
assert.equal(sessionsDb.getSessionById('app-run-2')?.provider_session_id, 'cursor-native-7');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type UseProjectsStateArgs = {
|
|||||||
*/
|
*/
|
||||||
type SessionUpsertedEvent = ServerEvent & {
|
type SessionUpsertedEvent = ServerEvent & {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
|
providerSessionId?: string | null;
|
||||||
provider: LLMProvider;
|
provider: LLMProvider;
|
||||||
session: ProjectSession;
|
session: ProjectSession;
|
||||||
project: {
|
project: {
|
||||||
@@ -212,6 +213,26 @@ const mergeProjectSessionPage = (
|
|||||||
return mergedProject;
|
return mergedProject;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSessionAliasIds = (event: SessionUpsertedEvent): Set<string> => {
|
||||||
|
const ids = new Set<string>();
|
||||||
|
const add = (value: unknown) => {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (trimmed) {
|
||||||
|
ids.add(trimmed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
add(event.sessionId);
|
||||||
|
add(event.providerSessionId);
|
||||||
|
add(event.session?.id);
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves which provider bucket on a `Project` holds sessions for a provider.
|
* Resolves which provider bucket on a `Project` holds sessions for a provider.
|
||||||
* The legacy payload keeps Claude sessions in `sessions` and the other
|
* The legacy payload keeps Claude sessions in `sessions` and the other
|
||||||
@@ -237,23 +258,47 @@ const providerBucketKey = (
|
|||||||
const upsertSessionIntoProject = (project: Project, event: SessionUpsertedEvent): Project => {
|
const upsertSessionIntoProject = (project: Project, event: SessionUpsertedEvent): Project => {
|
||||||
const bucketKey = providerBucketKey(event.provider);
|
const bucketKey = providerBucketKey(event.provider);
|
||||||
const bucket = project[bucketKey] ?? [];
|
const bucket = project[bucketKey] ?? [];
|
||||||
const existingIndex = bucket.findIndex((session) => session.id === event.sessionId);
|
const aliasIds = getSessionAliasIds(event);
|
||||||
|
const normalizedSession: ProjectSession = {
|
||||||
|
...event.session,
|
||||||
|
id: event.sessionId,
|
||||||
|
};
|
||||||
|
const existingIndex = bucket.findIndex((session) => aliasIds.has(String(session.id)));
|
||||||
|
|
||||||
let nextBucket: ProjectSession[];
|
let nextBucket: ProjectSession[];
|
||||||
|
let inserted = false;
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
const existing = bucket[existingIndex];
|
let changed = false;
|
||||||
const updated = { ...existing, ...event.session };
|
nextBucket = [];
|
||||||
if (serialize(existing) === serialize(updated)) {
|
|
||||||
|
for (const [index, session] of bucket.entries()) {
|
||||||
|
if (index === existingIndex) {
|
||||||
|
const updated = { ...session, ...normalizedSession };
|
||||||
|
if (serialize(session) !== serialize(updated)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
nextBucket.push(updated);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aliasIds.has(String(session.id))) {
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextBucket.push(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
nextBucket = [...bucket];
|
|
||||||
nextBucket[existingIndex] = updated;
|
|
||||||
} else {
|
} else {
|
||||||
nextBucket = [event.session, ...bucket];
|
nextBucket = [normalizedSession, ...bucket];
|
||||||
|
inserted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const next: Project = { ...project, [bucketKey]: nextBucket };
|
const next: Project = { ...project, [bucketKey]: nextBucket };
|
||||||
if (existingIndex < 0) {
|
if (inserted) {
|
||||||
const total = Number(project.sessionMeta?.total ?? 0) + 1;
|
const total = Number(project.sessionMeta?.total ?? 0) + 1;
|
||||||
next.sessionMeta = {
|
next.sessionMeta = {
|
||||||
...project.sessionMeta,
|
...project.sessionMeta,
|
||||||
@@ -629,10 +674,40 @@ export function useProjectsState({
|
|||||||
const updated = upsertSessionIntoProject(previousProject, upsert);
|
const updated = upsertSessionIntoProject(previousProject, upsert);
|
||||||
return updated === previousProject ? previousProject : updated;
|
return updated === previousProject ? previousProject : updated;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const aliasedSelectedSessionId =
|
||||||
|
typeof upsert.providerSessionId === 'string' && upsert.providerSessionId !== upsert.sessionId
|
||||||
|
? upsert.providerSessionId
|
||||||
|
: null;
|
||||||
|
if (!aliasedSelectedSessionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedSelectedSession: ProjectSession = {
|
||||||
|
...upsert.session,
|
||||||
|
id: upsert.sessionId,
|
||||||
|
__provider: upsert.provider,
|
||||||
|
__projectId: upsert.project?.projectId ?? currentSelectedSession?.__projectId,
|
||||||
|
};
|
||||||
|
|
||||||
|
setSelectedSession((previousSession) => {
|
||||||
|
if (previousSession?.id !== aliasedSelectedSessionId) {
|
||||||
|
return previousSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...previousSession,
|
||||||
|
...normalizedSelectedSession,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sessionId === aliasedSelectedSessionId) {
|
||||||
|
navigate(`/session/${upsert.sessionId}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return subscribe(handleEvent);
|
return subscribe(handleEvent);
|
||||||
}, [subscribe]);
|
}, [navigate, sessionId, subscribe]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user