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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
82 lines · 3.1 KB Raw History
/**
 * Permissioned data as a server extension: the platform builds the space
 * storage and passes the factory to core's `extensions` option; core mounts
 * the space endpoints, the account page's space routes and the admin port
 * behind them.
 */

import { createSpaceAccountRoutes } from './account.js';
import { createSpaceAdmin } from './admin.js';
import { createNotifyOutbox } from './outbox.js';
import { createSpaceRoutes } from './routes.js';

/**
 * @param {Object} [options]
 * @param {typeof fetch} [options.fetch] - for the outbound notification calls; defaults to the global fetch
 * @returns {import('@pdsjs/core/ports').ExtensionFactory}
 */
export function createSpacesExtension({ fetch: fetchImpl } = {}) {
  return (host) => {
    const { spaceStorage, actorStorage, blobs, didResolver, verifier } = host;
    if (!spaceStorage) {
      throw new Error('The spaces extension needs spaceStorage on the server');
    }
    if (!didResolver || !verifier) {
      throw new Error(
        'The spaces extension needs a didResolver and a verifier on the server',
      );
    }
    const spaceAdmin = createSpaceAdmin({ spaceStorage, getDid: host.getDid });
    const outbox = createNotifyOutbox({
      spaceStorage,
      getDid: host.getDid,
      getSigner: host.getSigner,
      resolveDid: didResolver,
      fetch: fetchImpl ?? ((url, init) => fetch(url, init)),
      scheduleWakeup: host.scheduleWakeup,
      background: host.background,
    });
    return {
      name: 'spaces',
      // The space endpoints hold records of every collection an app writes
      // into a space.
      collections: ['*'],
      routes: {
        ...createSpaceRoutes({
          spaceStorage,
          // A space record's blobs are ordinary account blobs, uploaded
          // through com.atproto.repo.uploadBlob and linked in the one blob
          // link table, so orphan cleanup sees a space reference the same as
          // a public one.
          blobs,
          linkBlob: (cid, uri, time) =>
            actorStorage.linkBlobToRecord(cid, uri, time),
          unlinkBlobs: (uri) => actorStorage.unlinkBlobsFromRecord(uri),
          getDid: host.getDid,
          // The full document: space auth verifies signatures against the
          // document's verification methods.
          resolveDid: didResolver,
          verifier,
          recordGuard: host.writeGuard,
          // Space tokens and service auth are signed by the account's atproto
          // signing key; its KEM key MACs reads of its space repos.
          getSigner: host.getSigner,
          getKemKey: host.getKemKey,
          fetch: fetchImpl ?? ((url, init) => fetch(url, init)),
          outbox,
          background: host.background,
        }),
        ...createSpaceAccountRoutes({
          host,
          spaceBrowser: host.spaceBrowser ?? null,
          spaceAdmin,
        }),
      },
      features: { spaceAdmin: true },
      // Write notices this repo still owes the authorities of spaces it
      // writes into, and deletion notices owed to the syncers of spaces it
      // deleted.
      tick: () => outbox.tick(),
    };
  };
}