import { BrowserOAuthClient } from '@atproto/oauth-client-browser' import { pageClientId, requestScope } from 'atlibs/oauth/index.js' import { createPluginContext } from 'atlibs/plugin-context/index.js' import { localModule } from 'atlibs/run-module/index.js' import { NAMES, loadPolicy, runAction } from './cocore-page.js' // Wiring only: one sign-in with the union of the modules' scopes, a form per action from module.json, // each run held to its own module's session by runAction. const base = new URL('./', import.meta.url) const ctx = createPluginContext({ id: 'cocore' }) const modules = Object.fromEntries(NAMES.map((name) => [name, localModule(new URL(`modules/${name}.wasm`, base), new URL(`modules/${name}.json`, base))])) const policy = await loadPolicy(base) const clientId = await pageClientId(requestScope(policy, NAMES)) const client = await BrowserOAuthClient.load({ clientId, handleResolver: 'https://bsky.social' }) const session = (await client.init().catch((e) => (alert(e.message), undefined)))?.session const $ = (selector) => document.querySelector(selector) $('#login').hidden = Boolean(session) $('#signed-in').hidden = !session if (session) $('#who').textContent = session.did $('#login').onsubmit = async (e) => { e.preventDefault() await client.signIn(new FormData(e.target).get('handle')).catch((error) => ($('#login-error').textContent = error.message)) } $('#logout').onclick = async () => { await client.revoke(session.did).catch(() => undefined) location.reload() } function build(tag, props = {}, children = []) { const node = Object.assign(document.createElement(tag), props) node.append(...children) return node } for (const name of NAMES) { const spec = await modules[name] // name, description, actions, calls... const section = build('details', {}, [build('summary', { textContent: `${spec.name}: ${spec.description}` })]) for (const action of spec.actions) { const out = build('pre', { className: 'out' }) const fields = (action.fields ?? []).map(({ key, label }) => build('label', { textContent: label }, [key === 'prompt' || key === 'bio' ? build('textarea', { name: key, rows: 3 }) : build('input', { name: key })])) const form = build('form', {}, [build('h4', { textContent: action.label }), build('small', { textContent: action.description ?? '' }), ...fields, build('button', { textContent: action.label }), out]) form.onsubmit = async (e) => { e.preventDefault() const button = form.querySelector('button') button.setAttribute('aria-busy', 'true') out.textContent = '' try { const input = Object.fromEntries([...new FormData(form)].filter(([, v]) => String(v).trim() !== '')) out.textContent = JSON.stringify(await runAction({ ctx, policy, modules, session }, name, action.entry, input), null, 2) } catch (error) { out.textContent = error.message } finally { button.removeAttribute('aria-busy') } } section.append(form) } $('#actions').append(section) }