export const SESSION_URL_QUERY_KEY = "c"; export const SESSION_URL_RETURN_STORAGE_KEY = "jyotisha.session-url-return"; export const SESSION_MISSING_NOTICE = "该对话不存在或已被删除"; export const SESSION_URL_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; export type SessionUrlQuery = { readonly present: boolean; readonly sessionId: string | null; }; export type BootstrapSessionSelection = { readonly sessionId: string; readonly urlAction: "keep" | "replace-selected" | "replace-clear" | "none"; readonly missing: boolean; readonly clearStoredReturn: boolean; }; export function parseSessionUrlQuery(search: string): SessionUrlQuery { const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search); if (!params.has(SESSION_URL_QUERY_KEY)) { return { present: false, sessionId: null }; } const raw = params.get(SESSION_URL_QUERY_KEY) ?? ""; return { present: true, sessionId: SESSION_URL_ID_PATTERN.test(raw) ? raw : null, }; } export function sessionHref(search: string, sessionId: string | null, pathname = "/"): string { const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search); if (sessionId) params.set(SESSION_URL_QUERY_KEY, sessionId); else params.delete(SESSION_URL_QUERY_KEY); const query = params.toString(); const path = pathname || "/"; return query ? `${path}?${query}` : path; } export function writeSessionUrl(sessionId: string | null, mode: "push" | "replace") { const { location, history } = globalThis.window; const next = sessionHref(location.search, sessionId, location.pathname); const current = `${location.pathname}${location.search}`; if (current === next) return; if (mode === "push") history.pushState(null, "", next); else history.replaceState(null, "", next); } export function persistLoginSessionReturn() { try { const sessionId = parseSessionUrlQuery(globalThis.window.location.search).sessionId; if (sessionId) globalThis.sessionStorage.setItem(SESSION_URL_RETURN_STORAGE_KEY, sessionId); } catch { // sessionStorage can throw in private mode; login still proceeds. } } export function readLoginSessionReturn(): string | null { try { const raw = globalThis.sessionStorage.getItem(SESSION_URL_RETURN_STORAGE_KEY); if (!raw || !SESSION_URL_ID_PATTERN.test(raw)) return null; return raw; } catch { return null; } } export function clearLoginSessionReturn() { try { globalThis.sessionStorage.removeItem(SESSION_URL_RETURN_STORAGE_KEY); } catch { // Ignore private-mode quota errors. } } export function resolveBootstrapSessionSelection(input: { readonly listedIds: readonly string[]; readonly defaultSessionId: string; readonly search: string; readonly storedReturnId: string | null; }): BootstrapSessionSelection { const query = parseSessionUrlQuery(input.search); if (query.present) { if (query.sessionId && input.listedIds.includes(query.sessionId)) { return { sessionId: query.sessionId, urlAction: "keep", missing: false, clearStoredReturn: true, }; } return { sessionId: input.defaultSessionId, urlAction: "replace-clear", missing: true, clearStoredReturn: true, }; } if (input.storedReturnId) { if (input.listedIds.includes(input.storedReturnId)) { return { sessionId: input.storedReturnId, urlAction: "replace-selected", missing: false, clearStoredReturn: true, }; } return { sessionId: input.defaultSessionId, urlAction: "replace-clear", missing: true, clearStoredReturn: true, }; } return { sessionId: input.defaultSessionId, urlAction: "none", missing: false, clearStoredReturn: false, }; }