import { Cubit } from 'atlibs/cubit/index.js' import { policyCall } from 'atlibs/oauth/index.js' import { hostCall, sessionXrpc } from 'atlibs/run-module/host-call.js' import { runModule } from 'atlibs/run-module/index.js' // Which session each module may use, and what that session's scope allows (a stage.json has this shape). export const POLICY = { sessions: { statusphere: { scope: 'atproto repo:xyz.statusphere.status' } }, modules: { statusphere: { session: 'statusphere' } }, } const IDLE = { status: 'idle', result: null, error: null } const INITIAL = { session: 'loading', did: null, error: null, count: IDLE, status: IDLE } // Page logic. Dependencies come in, so the page wires real ones and a test passes stubs: // ctx a PluginContext (createPluginContext) // client Promise of a BrowserOAuthClient // hello Promise of the hello module (localModule or loadModule) // statusphere Promise of the statusphere module export class HelloCubit extends Cubit { #ctx #client #hello #statusphere #session constructor({ ctx, client, hello, statusphere }) { super(INITIAL) this.#ctx = ctx this.#client = client this.#hello = hello this.#statusphere = statusphere } // Restore the last session, or finish a sign-in redirect. async init() { try { const signedIn = await (await this.#client).init() this.#session = signedIn?.session this.emit({ session: signedIn ? 'signed-in' : 'signed-out', did: signedIn?.session.did ?? null }) } catch (error) { this.emit({ session: 'error', error: error.message }) } } // Navigates to the user's authorization server; returns only if that fails. async login(handle) { try { await (await this.#client).signIn(handle) } catch (error) { this.emit({ error: error.message }) } } async logout() { await (await this.#client).revoke(this.#session.did).catch(() => undefined) this.#session = undefined this.emit({ ...INITIAL, session: 'signed-out' }) } // Signed out: hostCall with no session makes public reads only. async count({ did, collection }) { this.emit({ count: { ...IDLE, status: 'busy' } }) this.emit({ count: await this.#run(this.#hello, 'count', { did, collection }, hostCall({ ctx: this.#ctx })) }) } // Signed in: the user's OAuth session, held to statusphere's own scope by policyCall. async setStatus(status) { const session = this.#session const call = policyCall(POLICY, 'statusphere', { statusphere: hostCall({ ctx: this.#ctx, xrpc: sessionXrpc(session), canHeader: false }), }) this.emit({ status: { ...IDLE, status: 'busy' } }) this.emit({ status: await this.#run(this.#statusphere, 'set_status', { did: session.did, status }, call) }) } async #run(module, entry, input, call) { try { const { result, error } = await runModule(this.#ctx, await module, entry, input, call) return error === undefined ? { status: 'done', result, error: null } : { status: 'error', result: null, error } } catch (error) { return { status: 'error', result: null, error: error.message } } } }