johnandersen777.bsky.social/atlibs-fedproxy
fedproxy and ingress modules for atlibs: SSH public keys on your PDS, did:key ingress; pages and web tiles
0
0
import { Cubit } from 'atlibs/cubit/index.js'
import { runModule } from 'atlibs/run-module/index.js'
// Everything the page knows, in one place. Components render slices of it.
// session: "loading" | "signed-out" | "signed-in" | "error"
// keys.status: "idle" | "loading" | "loaded" | "error"
// create.status: "idle" | "busy" | "done" | "error"
const INITIAL = {
session: "loading",
handle: null,
error: null,
login: { busy: false, error: null },
keys: { status: "idle", services: [], error: null },
create: { status: "idle", error: null, uri: null },
};
// Page logic: the OAuth session, and the fedproxy wasm module's actions run as that session.
// Dependencies come in so the page wires real ones and a test can pass stubs:
// client Promise of a BrowserOAuthClient
// makeAgent session => @atproto/api Agent
// module Promise of { module: Uint8Array, calls: string[] }, the fedproxy wasm and its allowed calls
// ctx PluginContext providing ctx.wasm
export class FedproxyCubit extends Cubit {
#client;
#makeAgent;
#module;
#ctx;
#agent;
constructor({ client, makeAgent, module, ctx }) {
super(INITIAL);
this.#client = client;
this.#makeAgent = makeAgent;
this.#module = module;
this.#ctx = ctx;
}
// Restore the last session, or finish a login redirect.
async init() {
try {
const result = await (await this.#client).init();
if (!result) return this.emit({ session: "signed-out" });
this.#agent = this.#makeAgent(result.session);
const res = await this.#agent.com.atproto.server.getSession();
if (!res.success) throw new Error(JSON.stringify(res));
this.emit({ session: "signed-in", handle: res.data.handle });
} catch (error) {
return this.emit({ session: "error", error: `An error occurred: ${error.message}` });
}
await this.listKeys();
}
// Navigates away to the authorization server; only returns if that fails.
async login(identifier) {
this.emit({ login: { busy: true, error: null } });
try {
await (await this.#client).signIn(identifier, { signal: new AbortController().signal });
} catch (error) {
this.emit({ login: { busy: false, error: `Login error: ${error.message}` } });
}
}
async logout() {
await (await this.#client).revoke(this.#agent.did).catch(() => undefined);
this.#agent = undefined;
this.emit({ ...INITIAL, session: "signed-out" });
}
async listKeys() {
this.emit({ keys: { ...this.state.keys, status: "loading", error: null } });
try {
const { services } = await this.#run("list_keys");
this.emit({ keys: { status: "loaded", services, error: null } });
} catch (error) {
this.emit({ keys: { ...this.state.keys, status: "error", error: error.message } });
}
}
// The module validates the key and writes the record.
async createKey({ name, service, key }) {
this.emit({ create: { status: "busy", error: null, uri: this.state.create.uri } });
let created;
try {
created = await this.#run("create_key", { name, service, key });
} catch (error) {
return this.emit({ create: { status: "error", error: error.message, uri: this.state.create.uri } });
}
await this.listKeys();
this.emit({ create: { status: "done", error: null, uri: created.uri } });
}
async #run(entry, input = {}) {
const agent = this.#agent;
const xrpc = async (nsid, { params, input }) => (await agent.call(nsid, params, input)).data;
const ran = await runModule(this.#ctx, await this.#module, entry, { ...input, did: agent.did, handle: this.state.handle }, xrpc, { provides: [] });
if (ran.error !== undefined) throw new Error(ran.error);
return ran.result;
}
}