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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
140 lines · 4.6 KB Raw History
// The drive extension over a host with spaces on: a drive held in a space
// reads through the space browser and storage the host carries and writes
// through the spaces package, all reached from the account routes.
import { createAccountJwt } from '@pdsjs/core/auth';
import { PersonalDataServer } from '@pdsjs/core/pds';
import { createMemorySpaceStorage } from '@pdsjs/spaces/memory-storage';
import { makeSpaceRow } from '@pdsjs/spaces/space-row';
import { describe, expect, it } from 'vitest';
import { createDriveExtension } from '../src/extension.js';

const DID = 'did:plc:driveextension';
const SPACE = `at://${DID}/space/dev.pdsjs.drive.space/photos`;
const SECRET = 'drive-extension-secret';
const BASE = 'https://pds.example.com';

/** @param {{ownSpace?: boolean}} [opts] */
async function createServer({ ownSpace = true } = {}) {
  /** @type {Map<string, Uint8Array>} */
  const blobData = new Map();
  const actorStorage = /** @type {any} */ ({
    getDid: async () => DID,
    getRecord: async () => null,
    listRecords: async () => ({ records: [], cursor: null }),
    putBlock: async () => {},
    putRecord: async () => {},
    putBlob: async () => {},
    linkBlobToRecord: async () => {},
    deleteRecord: async () => {},
    unlinkBlobsFromRecord: async () => {},
  });
  const blobs = /** @type {any} */ ({
    put: async (
      /** @type {string} */ _did,
      /** @type {string} */ cid,
      /** @type {Uint8Array} */ data,
    ) => {
      blobData.set(cid, data);
    },
    get: async (/** @type {string} */ _did, /** @type {string} */ cid) => {
      const data = blobData.get(cid);
      return data ? { data, mimeType: 'application/octet-stream' } : null;
    },
  });
  const spaceStorage = createMemorySpaceStorage();
  // The account's own space: a write into one that does not exist is refused.
  if (ownSpace) {
    await spaceStorage.putSpace(makeSpaceRow(SPACE, { isOwner: true }));
  }
  return new PersonalDataServer({
    actorStorage,
    sharedStorage: /** @type {any} */ ({}),
    blobs,
    jwtSecret: SECRET,
    hostname: 'pds.example.com',
    blobUploadLimit: 64,
    spaceStorage,
    extensions: [createDriveExtension()],
  });
}

async function cookie() {
  return `pdsjs_account=${encodeURIComponent(await createAccountJwt(DID, SECRET))}`;
}

/**
 * @param {PersonalDataServer} pds
 * @param {string} path
 * @param {RequestInit & {json?: unknown}} [init]
 */
async function call(pds, path, init = {}) {
  const headers = new Headers(init.headers);
  headers.set('cookie', await cookie());
  if (init.method === 'POST') headers.set('origin', BASE);
  if (init.json !== undefined) headers.set('content-type', 'application/json');
  return pds.fetch(
    new Request(BASE + path, {
      ...init,
      headers,
      body: init.json !== undefined ? JSON.stringify(init.json) : init.body,
    }),
  );
}

describe('a drive held in a space', () => {
  it('makes a drive space through the admin port built over the storage', async () => {
    const pds = await createServer({ ownSpace: false });
    const made = await call(pds, '/account/api/drive/space', {
      method: 'POST',
      json: { name: 'work' },
    });
    expect(made.status).toBe(200);
    expect(await made.json()).toMatchObject({ key: 'work', name: 'work' });
    const listing = await call(pds, '/account/api/drive/files');
    expect(
      (await listing.json()).spaces.map(
        (/** @type {{name: string}} */ s) => s.name,
      ),
    ).toEqual(['work']);
  });

  it('creates a folder, stores a file and reads it back through the space', async () => {
    const pds = await createServer();

    const folder = await call(pds, '/account/api/drive/folder', {
      method: 'POST',
      json: { name: 'trips', space: SPACE },
    });
    expect(folder.status).toBe(200);

    const listing = await call(
      pds,
      `/account/api/drive/files?space=${encodeURIComponent(SPACE)}`,
    );
    expect(listing.status).toBe(200);
    expect(
      (await listing.json()).folders.map(
        (/** @type {{name: string}} */ f) => f.name,
      ),
    ).toEqual(['trips']);

    const upload = await call(
      pds,
      `/account/api/drive/upload?name=${encodeURIComponent('note.txt')}&space=${encodeURIComponent(SPACE)}`,
      {
        method: 'POST',
        headers: { 'content-type': 'text/plain' },
        body: 'hello from a space',
      },
    );
    expect(upload.status).toBe(200);
    const { rkey } = await upload.json();

    const file = await call(
      pds,
      `/account/api/drive/file?rkey=${encodeURIComponent(rkey)}&space=${encodeURIComponent(SPACE)}`,
    );
    expect(file.status).toBe(200);
    expect(await file.text()).toBe('hello from a space');
  });
});