Files
hapi/web/src/lib/codexImportedSessions.ts
T
f9ef3a4489 feat(codex): import local Codex sessions into Hapi (#796)
* local: add Codex Desktop session sync controls

* feat(codex): import local Codex sessions into Hapi

---------

Co-authored-by: Codex Local <codex-local@example.invalid>
2026-06-04 17:53:12 +08:00

95 lines
3.3 KiB
TypeScript

const CODEX_IMPORTED_SESSIONS_STORAGE_KEY = 'hapi.codexImportedSessions'
const CODEX_IMPORTED_SESSIONS_EVENT = 'hapi:codex-imported-sessions-updated'
type CodexImportedSessionsMap = Record<string, number>
function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof localStorage !== 'undefined'
}
function dispatchCodexImportedSessionsChanged(): void {
if (!isBrowser()) return
window.dispatchEvent(new CustomEvent(CODEX_IMPORTED_SESSIONS_EVENT))
}
export function readCodexImportedSessions(): CodexImportedSessionsMap {
if (!isBrowser()) return {}
try {
const raw = localStorage.getItem(CODEX_IMPORTED_SESSIONS_STORAGE_KEY)
if (!raw) return {}
const parsed = JSON.parse(raw)
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return {}
}
const result: CodexImportedSessionsMap = {}
for (const [key, value] of Object.entries(parsed)) {
if (typeof key === 'string' && typeof value === 'number' && Number.isFinite(value)) {
result[key] = value
}
}
return result
} catch {
return {}
}
}
function writeCodexImportedSessions(map: CodexImportedSessionsMap): void {
if (!isBrowser()) return
localStorage.setItem(CODEX_IMPORTED_SESSIONS_STORAGE_KEY, JSON.stringify(map))
dispatchCodexImportedSessionsChanged()
}
export function markCodexSessionsImported(codexSessionIds: string[], importedAt = Date.now()): void {
if (!isBrowser() || codexSessionIds.length === 0) return
// 中文注释:以 Codex thread ID 为 key 记录导入时间,便于会话列表把时间文案切换成“从 Codex 客户端导入”。
const next = readCodexImportedSessions()
for (const codexSessionId of codexSessionIds) {
const trimmed = codexSessionId.trim()
if (trimmed) {
next[trimmed] = importedAt
}
}
writeCodexImportedSessions(next)
}
export function clearCodexImportedSession(codexSessionId: string | null | undefined): void {
if (!isBrowser() || !codexSessionId) return
const next = readCodexImportedSessions()
if (!(codexSessionId in next)) return
// 中文注释:当用户已经在 Hapi 内继续这个会话后,移除导入标记,列表时间恢复为普通“xx 分钟前”。
delete next[codexSessionId]
writeCodexImportedSessions(next)
}
export function getCodexImportedAt(codexSessionId: string | null | undefined): number | null {
if (!codexSessionId) return null
const importedAt = readCodexImportedSessions()[codexSessionId]
return typeof importedAt === 'number' && Number.isFinite(importedAt) ? importedAt : null
}
export function subscribeCodexImportedSessions(onChange: () => void): () => void {
if (!isBrowser()) {
return () => {}
}
const handleStorage = (event: StorageEvent) => {
if (event.key === CODEX_IMPORTED_SESSIONS_STORAGE_KEY) {
onChange()
}
}
const handleCustomEvent = () => {
onChange()
}
window.addEventListener('storage', handleStorage)
window.addEventListener(CODEX_IMPORTED_SESSIONS_EVENT, handleCustomEvent)
return () => {
window.removeEventListener('storage', handleStorage)
window.removeEventListener(CODEX_IMPORTED_SESSIONS_EVENT, handleCustomEvent)
}
}