// The page's pure parts: the did:key and what load shows, which DNS records a CNAME replaces, and a handler
// run as the relay's response.
// The ingress did:key: its JWK sealed in the plugin-context secret setting `didkeys` (slot `ingress`), made
// on first load. One tab at a time reads and writes the map (Web Locks). Storage that fails (a private
// window, blocked site data) gives a key for this load only: kept false.
export async function ingressKey(settings, createDidKey, locks = globalThis.navigator?.locks) {
const locked = (fn) => (locks ? locks.request('atlibs-didkeys', fn) : fn())
try {
return await locked(async () => {
const keys = (await settings.get('didkeys')) ?? {}
const key = await createDidKey(keys.ingress)
if (!keys.ingress) await settings.set('didkeys', { ...keys, ingress: key.jwk })
return { key, kept: true }
})
} catch {
return { key: await createDidKey(), kept: false }
}
}
// Load, signed in or out: the tab serves at its did:key host (start(key, aliases) runs the ingress), and that URL
// shows. The domain form (CNAME + _xrpc TXT through Marque) needs a session: signed out it is disabled.
// $ document.querySelector
// hostOf did -> the relay's host name for it
export async function boot({ $, settings, createDidKey, hostOf, start, signedIn, locks }) {
const { key, kept } = await ingressKey(settings, createDidKey, locks)
const host = hostOf(key.did)
Object.assign($('#urls .ingress'), { href: `https://${host}/`, textContent: `https://${host}/` })
$('#urls .kept').textContent = kept ? '' : '(this browser keeps no site data: a new did:key, and URL, each load)'
$('#urls').hidden = false
$('#serve fieldset').disabled = !signedIn
$('#serve .signin').hidden = signedIn
start(key, [])
return { key, host }
}
// The handler the code view starts with. run.input is the forwarded request.
export const DEFAULT_HANDLER = `// One run per request to the ingress. run.input is the request:
// { method, path, params, headers, body, callerDid }
// Finish with a string (200, text/plain) or { status, body, contentType, headers }, e.g.
// run.finish(Json.object()
// .set("contentType", Json.string("text/html"))
// .set("body", Json.string("
" + run.input.get("path").str + "
")));
export function main(run: Run): void {
run.finish(Json.string("hello world"));
}
`
export const fqdn = (name, domain) => (name === '@' ? domain : `${name}.${domain}`)
// A CNAME is alone at its name (RFC 1034 3.6.2): every other record there goes. `create` is false when
// the CNAME is already in the zone. Names and hostnames compare as Marque stores them (lowercase, no dot).
export function plan(records, name, target) {
const here = records.filter((r) => r.name === name)
const same = (r) => r.recordType === 'CNAME' && r.value === target
return { remove: here.filter((r) => !same(r)), create: !here.some(same) }
}
// The relay maps a custom host by the TXT at _xrpc., whose value is the ingress did:key (Marque
// flattens the CNAME, so the relay cannot follow it). A did:key TXT there that is not ours (an older key) is
// updated; other TXT records at the name stay.
export const txtName = (name) => (name === '@' ? '_xrpc' : `_xrpc.${name}`)
export function txtPlan(records, name, did) {
const here = records.filter((r) => r.name === name && r.recordType === 'TXT')
if (here.some((r) => r.value === did)) return { update: null, create: false }
const stale = here.find((r) => r.value.startsWith('did:key:')) ?? null
return { update: stale, create: !stale }
}
// runModule's { result, error } for the handler -> the forward call's { status, body, contentType }.
export function toResponse({ result, error }) {
const text = (status, body) => ({ status, body, contentType: 'text/plain; charset=utf-8' })
if (error !== undefined) return text(500, `handler failed: ${error}`)
const value = result?.value ?? null
if (value === null) return text(500, 'the handler did not call run.finish')
if (typeof value === 'string') return text(200, value)
if (typeof value === 'object' && !Array.isArray(value) && ('status' in value || 'body' in value || 'contentType' in value || 'headers' in value)) {
const body = value.body ?? ''
const response = { status: value.status ?? 200, body, contentType: value.contentType ?? (typeof body === 'string' ? 'text/plain; charset=utf-8' : 'application/json') }
// name -> string, or -> [string] for Set-Cookie; the ingress module and the relay drop the rest
if (value.headers && typeof value.headers === 'object' && !Array.isArray(value.headers)) response.headers = value.headers
return response
}
return { status: 200, body: value, contentType: 'application/json' }
}