80 lines
4.3 KiB
JavaScript
80 lines
4.3 KiB
JavaScript
import { plainMessage } from './org-admin.mjs';
|
|
// The registry and service are injected here so their single, shared implementations remain
|
|
// the authority. The browser supplies only an id; roots and sockets come from verified metadata.
|
|
// Only the service's own coded messages are shown, never parser text, which can quote file bytes.
|
|
function publicReason(ctx, error) {
|
|
if (typeof error?.code !== 'string' || typeof error.message !== 'string') return 'organization could not be read';
|
|
return plainMessage(ctx, error.message.replace(/^(invalid registry):[\s\S]*$/, '$1'));
|
|
}
|
|
export function organizationConsole({ ctx, listOrgs, showOrg, resolveOrg, orgPaths, busStatus, options }) {
|
|
async function admitted(id, session) {
|
|
const principal = { provider: session?.provider, iss: session?.iss, sub: session?.sub };
|
|
if (Object.values(principal).some(value => typeof value !== 'string' || !value)) throw new Error('org-unavailable');
|
|
const context = { ...ctx, principal };
|
|
const shown = await showOrg(context, id);
|
|
const org = await resolveOrg(ctx, id);
|
|
if (org.state !== 'active') throw new Error('org-unavailable');
|
|
return { org, shown, context };
|
|
}
|
|
async function list(session) {
|
|
const entries = await listOrgs(ctx), result = [];
|
|
for (const entry of entries) {
|
|
if (entry.state !== 'active') continue;
|
|
let verified;
|
|
try { verified = await admitted(entry.id, session); }
|
|
catch (error) {
|
|
// Show a broken org only to its verified owner (the service marks failures after the
|
|
// owner check). Anything else, including a broken root or owner file, stays hidden.
|
|
if (error?.ownerVerified !== true) continue;
|
|
result.push({ id: entry.id, label: entry.id, invalid: true, reason: publicReason(ctx, error) });
|
|
continue;
|
|
}
|
|
let busHealthy = false;
|
|
try { busHealthy = (await busStatus(ctx, entry.id)).healthy === true; } catch { /* offline metadata remains visible */ }
|
|
result.push({ id: entry.id, label: entry.id, theme: verified.shown.doc?.appearance?.theme ?? null, busHealthy });
|
|
}
|
|
return result;
|
|
}
|
|
// U1 (#1873): the orgs a session belongs to, for the cross-organization read (workspace-read.mjs).
|
|
// The same admission as `list` and `resolve`: an org the session does not own is skipped, and a
|
|
// broken org shows only to its verified owner, as invalid. Bus status and options are lazy, so a
|
|
// stopped bus never gets a transport; `timeoutMs` bounds each bus read the options make.
|
|
async function members(session, { timeoutMs } = {}) {
|
|
const entries = await listOrgs(ctx), result = [];
|
|
for (const entry of entries) {
|
|
if (entry.state !== 'active') continue;
|
|
let verified;
|
|
try { verified = await admitted(entry.id, session); }
|
|
catch (error) {
|
|
if (error?.ownerVerified !== true) continue;
|
|
result.push({ id: entry.id, invalid: true, reason: publicReason(ctx, error) });
|
|
continue;
|
|
}
|
|
const { org, shown, context } = verified, id = entry.id;
|
|
result.push({
|
|
id, doc: shown.doc,
|
|
// Host paths in a refusal or boot reason become relative to the instance before they leave.
|
|
async status(bound) {
|
|
let s;
|
|
try { s = await busStatus(ctx, id, bound); }
|
|
catch (error) { throw new Error(plainMessage(ctx, typeof error?.message === 'string' ? error.message : 'bus state could not be read')); }
|
|
const boot = s?.boot && typeof s.boot === 'object' ? { ok: s.boot.ok, at: s.boot.at, lastGood: s.boot.lastGood ?? null, reason: s.boot.reason == null ? null : plainMessage(ctx, s.boot.reason) } : null;
|
|
return { healthy: s?.healthy === true, pid: Number.isSafeInteger(s?.pid) ? s.pid : null, boot };
|
|
},
|
|
options: bound => options({ id, root: org.root, paths: orgPaths(org.root), session, context, doc: shown.doc, ...(timeoutMs === undefined ? {} : { timeoutMs }), ...bound }),
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
return Object.freeze({
|
|
list,
|
|
members,
|
|
async resolve(id, session) {
|
|
const { org, shown, context } = await admitted(id, session);
|
|
let busHealthy = false;
|
|
try { busHealthy = (await busStatus(ctx, id)).healthy === true; } catch { /* unavailable */ }
|
|
return { ...options({ id, root: org.root, paths: orgPaths(org.root), session, context, doc: shown.doc }), busHealthy };
|
|
},
|
|
});
|
|
}
|