/** * Creates the skeleton-owned side of an external session integration. * Credential acquisition and storage stay inside the supplied external owner. * * @param {{ * readState(): import("../../application/ports/auth-session-port.js").SessionState, * subscribe(listener: () => void): () => void, * beginSignIn(returnTo?: string): Promise, * signOut(): Promise, * attachCredential(request: Request): Promise, * recoverSession(): Promise<"restored" | "no-session">, * notifyUnauthenticated(): void * }} owner * @returns {import("../../application/ports/auth-session-port.js").AuthSessionPort} */ export function createExternalAuthSessionAdapter(owner) { return Object.freeze({ getState() { return owner.readState(); }, subscribe(listener) { return owner.subscribe(listener); }, async beginSignIn(returnTo) { await owner.beginSignIn(returnTo); }, async signOut() { await owner.signOut(); }, /** @param {Request} request */ async attach(request) { const attached = await owner.attachCredential(request); if (!(attached instanceof Request)) { throw new TypeError("Auth owner returned an invalid request"); } return attached; }, async recover() { const result = await owner.recoverSession(); if (result !== "restored" && result !== "no-session") { throw new TypeError("Auth owner returned an invalid recovery state"); } return result; }, onUnauthenticated() { owner.notifyUnauthenticated(); }, }); } export function createAnonymousSessionAdapter() { return createExternalAuthSessionAdapter({ readState: () => "unauthenticated", subscribe: () => () => {}, beginSignIn: async () => {}, signOut: async () => {}, attachCredential: async (request) => request, recoverSession: async () => "no-session", notifyUnauthenticated: () => {}, }); } /** * Local/test-only session seam. It never creates or stores credentials. * * @param {import("../../application/ports/auth-session-port.js").SessionState} [initialState] */ export function createDemoSessionAdapter(initialState = "unauthenticated") { let state = initialState; const listeners = new Set(); function notify() { for (const listener of listeners) listener(); } /** @param {import("../../application/ports/auth-session-port.js").SessionState} next */ function setState(next) { state = next; notify(); } return Object.freeze({ getState: () => state, /** @param {() => void} listener */ subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }, async beginSignIn() { setState("authenticated"); }, async signOut() { setState("unauthenticated"); }, /** @param {Request} request */ async attach(request) { return request; }, async recover() { if (state === "recovery-pending") { setState("authenticated"); return /** @type {const} */ ("restored"); } return /** @type {const} */ ("no-session"); }, onUnauthenticated() { setState("unauthenticated"); }, setState, }); } export function createUnavailableSessionAdapter() { return Object.freeze({ getState: () => /** @type {const} */ ("integration-failed"), subscribe: () => () => {}, beginSignIn: async () => {}, signOut: async () => {}, /** @param {Request} request */ attach: async (request) => request, recover: async () => /** @type {const} */ ("no-session"), onUnauthenticated: () => {}, }); }