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 { BrowserOAuthClient } from '@atproto/oauth-client-browser'
import { Agent } from '@atproto/api'
import { Tile, TileMothership } from '@dasl/tile-loader'
import { ATTileLoader } from '@dasl/tile-loader/at'
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'
// The host (web tiles' "mothership"): holds the OAuth session and runs the fedproxy wasm module,
// with the same FedproxyCubit as web/fedproxy-ssh-public-keys. The UI is a web tile
// (https://dasl.ing/tiles.html): a frame on a random origin, no network, resources from its
// manifest. It gets cubit state over tp-data and sends { createKey } back; nothing else crosses.
// ?module=local|<handle>/<name>|at://... wasm module, as in fedproxy-ssh-public-keys
// ?tile=local|at://... tile; local serves ./tile/ through the same sandbox
// ?load=<host> tile loading server, default load.webtil.es
const params = new URLSearchParams(location.search)
const MODULE = params.get('module') ?? 'bobvmbuilder.bsky.social/fedproxy'
// atlibs publish tile web/fedproxy-webtiles/tile fedproxy-webtiles
const TILE = params.get('tile') ?? 'at://did:plc:lpfuqerea3deuoyrn7ojser4/ing.dasl.masl/3mzt2ys2vqc3l'
const ctx = createPluginContext({ id: 'fedproxy' })
const MODULES = new URL('./modules/', import.meta.url)
// Loads "local" from ./tile/: manifest.json as the MASL, files as its resources.
const localTiles = {
async load(url, mothership) {
if (url !== 'local') return false
const manifest = await (await fetch('./tile/manifest.json')).json()
return new Tile(mothership, url, manifest, {
async resolvePath(path) {
const response = await fetch(`./tile${path === '/' ? '/index.html' : path}`)
if (!response.ok) return { ok: false, status: response.status }
return { ok: true, status: 200, headers: { 'content-type': response.headers.get('content-type') }, body: new Uint8Array(await response.arrayBuffer()) }
},
})
},
}
// As fedproxy-ssh-public-keys: the loopback client on localhost; deployed, the client `atlibs stage
// --target spa` wrote next to the page, else fedproxy.com's own.
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 mothership = new TileMothership({ loadDomain: params.get('load') ?? 'load.webtil.es' })
mothership.init()
mothership.addLoader(localTiles)
mothership.addLoader(new ATTileLoader())
const show = (id, on) => (document.getElementById(id).style.display = on ? 'inherit' : 'none')
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 button = document.getElementById('login-button')
if (state.login.busy) button.setAttribute('aria-busy', 'true')
else button.removeAttribute('aria-busy')
document.getElementById('login-form-error').innerText = state.login.error ?? ''
}
// What the tile may see: what it renders, no session errors or login state.
const forTile = ({ session, handle, keys, create }) => ({ session, handle, keys, create })
async function init() {
document.getElementById('login-form').onsubmit = (e) => {
e.preventDefault()
cubit.login(e.target.username.value)
}
document.getElementById('logout-nav').onclick = (e) => {
e.preventDefault()
cubit.logout()
}
const tile = await mothership.loadTile(TILE)
if (!tile) throw new Error(`No tile at ${TILE}`)
// tp-data delivers only to handlers the tile has added, so answer every ready, then send on change.
let ready = false
tile.addEventListener('data-ready', () => {
ready = true
tile.sendData(forTile(cubit.state))
})
// The tile is untrusted: take only the one request it may make, as strings. The module validates them.
tile.addEventListener('data', ({ payload }) => {
const { name, service, key } = payload?.createKey ?? {}
if ([name, service, key].every((v) => typeof v === 'string')) cubit.createKey({ name, service, key })
})
document.getElementById('tile').append(tile.renderContent(tile.manifest.sizing?.height ?? 900))
cubit.subscribe((state) => {
renderPage(state)
if (ready) tile.sendData(forTile(state))
})
await cubit.init()
}
document.addEventListener('DOMContentLoaded', init)