// A space's policy, read off the wire and written back to it. // // The policy is an open union. Its `$type` names one of the three baseline // policies, and its other fields configure that policy. /** @type {Record} */ const POLICY_TYPES = { personalspace: 'com.atproto.personalspace.policy', memberspace: 'com.atproto.memberspace.policy', managedspace: 'com.atproto.managedspace.policy', }; /** The kinds a page offers, in the order it offers them. */ export const POLICY_KINDS = ['memberspace', 'personalspace', 'managedspace']; /** @type {Record} */ export const POLICY_LABELS = { personalspace: 'The authority alone', memberspace: 'Members on a list', managedspace: 'Whoever the managing app admits', }; export const ACCESS_KINDS = ['open', 'allowList']; /** @type {Record} */ export const ACCESS_LABELS = { open: 'Any client', allowList: 'Clients on an allow list', }; /** * @param {any} config - a getSpace answer * @returns {Record} */ function policyOf(config) { const policy = config?.policy; return policy && typeof policy === 'object' ? policy : {}; } /** * Which policy governs a space, or null for one this app does not know. * @param {any} config * @returns {string|null} */ export function policyKind(config) { const type = policyOf(config).$type; const found = Object.entries(POLICY_TYPES).find(([, name]) => name === type); return found ? found[0] : null; } /** * Whether a memberspace lets anyone read, or anyone's writes count. * @param {any} config * @param {'read'|'write'} access * @returns {boolean} */ export function isPublic(config, access) { if (policyKind(config) !== 'memberspace') return false; const policy = policyOf(config); return (access === 'read' ? policy.publicRead : policy.publicWrite) === true; } /** * The app a managedspace defers each decision to, or null. * @param {any} config * @returns {string|null} */ export function managingAppOf(config) { const app = policyOf(config).managingApp; return typeof app === 'string' && app ? app : null; } /** * Whether a memberspace restricts the apps that may read it. * @param {any} config * @returns {string} */ export function accessKind(config) { return Array.isArray(policyOf(config).allowedApps) ? 'allowList' : 'open'; } /** * The client IDs a memberspace allows. * @param {any} config * @returns {string[]} */ export function allowedOf(config) { const allowed = policyOf(config).allowedApps; return Array.isArray(allowed) ? allowed.filter((entry) => typeof entry === 'string') : []; } /** * Whether the policy asks the authority to publish it, or its member list, * as records. * @param {any} config * @param {'publishPolicy'|'publishMembers'} field * @returns {boolean} */ export function publishes(config, field) { return policyOf(config)[field] === true; } /** * The policy union to send. * @param {string} kind * @param {Object} [fields] * @param {boolean} [fields.publicRead] * @param {boolean} [fields.publicWrite] * @param {string[]|null} [fields.allowedApps] - null lets any app read * @param {string|null} [fields.managingApp] * @param {boolean} [fields.publishPolicy] * @param {boolean} [fields.publishMembers] * @returns {Record} */ export function policyUnion(kind, fields = {}) { const $type = POLICY_TYPES[kind]; if (kind === 'memberspace') { return { $type, publicRead: fields.publicRead ?? false, publicWrite: fields.publicWrite ?? false, ...(fields.allowedApps ? { allowedApps: fields.allowedApps } : {}), publishPolicy: fields.publishPolicy ?? false, publishMembers: fields.publishMembers ?? false, }; } if (kind === 'managedspace') { return { $type, managingApp: fields.managingApp, publishPolicy: fields.publishPolicy ?? false, }; } return { $type }; }