~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs

0
0
main
85 lines · 3.0 KB Raw History
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 }
		}
	}
}