flotilla/src/app/session.ts

105 lines
3.5 KiB
TypeScript
Raw Normal View History

2026-07-28 16:16:26 +00:00
import type {ClientOptions} from "@pomade/core"
import type {Wallet} from "@welshman/util"
import {nip01, nip07, nip46, nip55, pomade, toSession} from "@welshman/app"
import type {Session} from "@welshman/app"
2026-08-06 15:43:57 +00:00
import {getTestEvents, maybeGetTestSession} from "@lib/test/session"
2026-07-28 16:16:26 +00:00
import {app, login, session} from "@app/core"
import {wallet} from "@app/lightning"
import {kv, ss, storage} from "@app/storage"
2026-06-08 21:14:40 +00:00
import {deactivateCurrentPomadeSession} from "@app/pomade"
import {Push} from "@app/push"
2026-03-05 22:20:20 +00:00
2026-07-28 16:16:26 +00:00
// Sessions used to be a Record<pubkey, session> keyed by an active pubkey, and carried their
// pubkey plus method-specific fields inline. Convert one so upgrading doesn't log people out.
type LegacySession = {
method: string
pubkey: string
secret?: string
signer?: string
email?: string
clientOptions?: ClientOptions
handler?: {pubkey: string; relays: string[]}
wallet?: Wallet
}
const toCurrentSession = (legacy: LegacySession): Session | undefined => {
switch (legacy.method) {
case "nip01":
return legacy.secret ? toSession(nip01, {secret: legacy.secret}) : undefined
case "nip07":
return toSession(nip07, {})
case "nip46":
return legacy.secret && legacy.handler
? toSession(nip46, {
clientSecret: legacy.secret,
signerPubkey: legacy.handler.pubkey,
relays: legacy.handler.relays,
})
: undefined
case "nip55":
return legacy.signer
? toSession(nip55, {pubkey: legacy.pubkey, signer: legacy.signer})
: undefined
case "pomade":
return legacy.clientOptions && legacy.email
? toSession(pomade, {clientOptions: legacy.clientOptions, email: legacy.email})
: undefined
}
}
const readLegacySession = async () => {
const pubkey = await kv.get<string>("pubkey")
const sessions = await ss.get<Record<string, LegacySession>>("sessions")
const legacy = pubkey ? sessions?.[pubkey] : undefined
if (legacy?.wallet) {
wallet.set(legacy.wallet)
}
return legacy ? toCurrentSession(legacy) : undefined
}
// The session is derived from the app's user, so it can't be synced to storage directly —
// read it back once at startup, then persist it whenever the identity changes.
export const restoreSession = async () => {
2026-08-06 15:43:57 +00:00
// Test-only: when Playwright has injected window.__TEST_SESSION__, that identity wins over
// whatever is in storage. No-op for real users; stripped from production builds.
const testSession = import.meta.env.DEV ? maybeGetTestSession() : undefined
const $session = testSession ?? (await ss.get<Session>("session")) ?? (await readLegacySession())
2026-07-28 16:16:26 +00:00
if ($session) {
await login($session)
}
2026-08-06 15:43:57 +00:00
// Logging in builds a fresh app, so the test's cached events go into its repository afterwards,
// the same way storage loads what the last session left behind. Storage gets there first and
// loads with Repository.load, which clears the repository before inserting, so these have to
// wait for it or they are wiped before anything reads them.
if (testSession) {
await storage.get()?.ready
for (const event of getTestEvents()) {
app.get().repository.publish(event)
}
}
2026-07-28 16:16:26 +00:00
return session.subscribe($session => {
if ($session) {
ss.set("session", $session)
}
})
}
2026-03-05 22:20:20 +00:00
export const logout = async () => {
await deactivateCurrentPomadeSession()
2026-03-05 22:20:20 +00:00
await Push.disable()
await kv.clear()
2026-03-24 00:51:51 +00:00
await ss.clear()
2026-07-28 16:16:26 +00:00
await storage.get()?.clear()
2026-03-05 22:20:20 +00:00
2026-07-28 16:16:26 +00:00
app.get().cleanup()
2026-03-05 22:20:20 +00:00
localStorage.clear()
window.location.href = "/"
}