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

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
main
76 lines · 3.6 KB Raw History
import { BrowserOAuthClient } from '@atproto/oauth-client-browser'
import { Agent } from '@atproto/api'

import { initATLibs } from './atlibs_element.js'
import { FedproxyCubit } from './fedproxy-cubit.js'
import { pageClientId } from 'atlibs/oauth/index.js'
import { createPluginContext } from 'atlibs/plugin-context/index.js'
import { loadModule, localModule } from 'atlibs/run-module/index.js'

// Wiring only: user events go to the cubit, cubit state goes to the views.

// The fedproxy wasm module owns every atproto call and fedproxy rule. By default the page runs
// the published module record (atlibs publish module fedproxy); ?module=local runs the
// `cargo wasm` build through the modules/ symlinks, and ?module=<handle>/<name> (or at://...) any other record.
const PUBLISHED = 'bobvmbuilder.bsky.social/fedproxy';
const MODULE = new URLSearchParams(location.search).get('module') ?? PUBLISHED;
const MODULES = new URL('./modules/', import.meta.url);

const ctx = createPluginContext({ id: 'fedproxy' });

// On localhost the loopback development client; deployed, the client `atlibs stage --target spa`
// wrote next to the page, else fedproxy.com's own (its deployment serves /oauth-client-metadata.json).
const SCOPE = 'atproto repo:com.fedproxy.sshPublicKey?action=create';
const clientId = pageClientId(SCOPE, { fallback: `https://${location.host}/oauth-client-metadata.json` });

const cubit = new FedproxyCubit({
	client: clientId.then((clientId) => BrowserOAuthClient.load({ clientId, handleResolver: 'https://bsky.social' })),
	makeAgent: (session) => new Agent(session),
	module: MODULE === 'local' ? localModule(new URL('fedproxy.wasm', MODULES), new URL('fedproxy.json', MODULES)) : loadModule(ctx, MODULE),
	ctx,
});

const show = (id, on) => (document.getElementById(id).style.display = on ? "inherit" : "none");

// The page's own sections; the two components render their slices themselves.
function renderPage(state) {
	show("loading-spinner", state.session === "loading" || state.session === "error");
	show("loading-error", state.session === "error");
	document.getElementById("loading-error").innerText = state.error ?? "";
	show("login-container", state.session === "signed-out");
	show("logout-nav", state.session === "signed-in");
	const loginButton = document.getElementById("login-button");
	if (state.login.busy) loginButton.setAttribute("aria-busy", "true");
	else loginButton.removeAttribute("aria-busy");
	document.getElementById("login-form-error").innerText = state.login.error ?? "";
	show("success-container", state.create.uri !== null);
	if (state.create.uri) document.getElementById("success-pdsls").href = `https://pdsls.dev/${state.create.uri}`;
}

async function init() {
	await initATLibs(window.customElements);
	// Their markup is fetched from .html templates, so wait before rendering into them.
	await Promise.all(["ssh-public-key-form", "ssh-public-keys-list"].map((name) => customElements.whenDefined(name)));
	const form = document.querySelector("ssh-public-key-form");
	const list = document.querySelector("ssh-public-keys-list");

	document.getElementById("login-form").onsubmit = (e) => {
		e.preventDefault();
		cubit.login(e.target.username.value);
	};
	document.getElementById("bsky-button").onclick = () => cubit.login("https://bsky.social");
	document.getElementById("logout-nav").onclick = (e) => {
		e.preventDefault();
		cubit.logout();
	};
	document.addEventListener("create-key", (e) => cubit.createKey(e.detail));

	cubit.subscribe((state, previous) => {
		renderPage(state);
		form.render(state, previous);
		list.render(state, previous);
	});
	await cubit.init();
}

document.addEventListener('DOMContentLoaded', init);