chat / client / test / helpers / orbit-address.mjs
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
/**
 * Boot a real room log and read its OrbitDB address — shared by
 * test/vectors/generate-orbit-address.mjs (against the TypeScript build) and
 * test/orbit-address.test.mjs (against the ClojureScript build), so both
 * implementations are driven through the identical sequence.
 *
 * THE interop invariant (PROTOCOL.md §5): the address is the CID of the manifest
 * {name, type, accessController} — a pure function of the secret-derived dbName
 * and the hardened access controller's own address. Nothing negotiates it and
 * nothing fails loudly if it diverges; a diverged build silently replicates a
 * database nobody else is in.
 *
 * What is real here: @orbitdb/core with entry+payload encryption and the sueta
 * identity provider, the hardened IPFS access controller, Helia on the
 * IndexedDB blockstore the browser build uses (fake-indexeddb standing in for
 * the browser's), and a real libp2p — helia gates on `isLibp2p()`, so a stub is
 * not an option.
 *
 * What is not: the libp2p node is built HERE with websockets only, instead of
 * through lib/net's `Net.create`. Net's browser stack adds @libp2p/webrtc and
 * window listeners, neither of which exists in node; and the address depends on
 * the transport list not at all. Net's own behaviour is pinned separately by the
 * membership transcripts in test/machines.test.mjs.
 */
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

/** 32 × 0x01, base64url — the seed rooms-kit's fixtures use, so `myAuthorId`
 *  is directly comparable across the two repos. */
export const SEED = "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE";

/**
 * Case 1 and 2 are the same room opened by an identity-carrying and an
 * identity-less member: the manifest hashes only {name, type, accessController},
 * never the creator, so they MUST land on one address. Case 3 is the same
 * secret under a different app salt: a different room.
 *
 * Cases 1–3 duplicate rooms-kit/test/vectors/orbit-address.json exactly (same
 * secrets, same salts, same seed) — chat's crypto/access derivations are
 * byte-identical to that repo's TypeScript ancestor, so the two fixtures must
 * agree, and test/orbit-address.test.mjs asserts they do.
 */
export const addressCases = [
  { secret: "A".repeat(43), appSalt: "chat.ardegazu.ro/v2", withIdentity: true },
  { secret: "A".repeat(43), appSalt: "chat.ardegazu.ro/v2", withIdentity: false },
  { secret: "B".repeat(43), appSalt: "board.ardegazu.ro/v1", withIdentity: true },
];

/** A real libp2p with node-capable transports only. */
async function nodeLibp2p() {
  const { createLibp2p } = await import("libp2p");
  const { webSockets } = await import("@libp2p/websockets");
  const { noise } = await import("@chainsafe/libp2p-noise");
  const { yamux } = await import("@chainsafe/libp2p-yamux");
  const { identify } = await import("@libp2p/identify");
  const { gossipsub } = await import("@chainsafe/libp2p-gossipsub");
  return createLibp2p({
    addresses: { listen: [] },
    transports: [webSockets()],
    connectionEncrypters: [noise()],
    streamMuxers: [yamux()],
    services: { identify: identify(), pubsub: gossipsub({ allowPublishToZeroTopicPeers: true }) },
  });
}

/**
 * @param mods {{RoomCrypto, openHelia, RoomLog}} the implementation under test
 * @param c    one entry of `addressCases`
 */
export async function openAddressCase(mods, c) {
  // RoomLog.open defaults its OrbitDB directory base to the relative literal
  // "sueta" (no caller here passes a `directory` hook), so it lands under the
  // CWD in node — run from a temp dir so nothing is written into the repo.
  const cwd = process.cwd();
  process.chdir(mkdtempSync(join(tmpdir(), "sueta-addr-")));
  const libp2p = await nodeLibp2p();
  try {
    const { Identity } = await import("ardegazu-id-kit");
    const rc = await mods.RoomCrypto.create(c.secret, c.appSalt, "12D3KooWFixed");
    // unique store names per case: two rooms must never share a blockstore
    const tag = `${c.appSalt}-${c.withIdentity}`.replace(/[^a-z0-9]+/gi, "-");
    const helia = await mods.openHelia(libp2p, { blocks: `b-${tag}`, data: `d-${tag}` });
    const identity = c.withIdentity ? await Identity.fromSeed(SEED) : null;
    const log = await mods.RoomLog.open(helia, rc, identity, { onEntry() {}, onError() {} });
    const result = {
      ...c,
      seed: c.withIdentity ? SEED : null,
      dbName: await rc.dbName(),
      address: log.address,
      // the durable author id: the identity pub for a sueta identity, OrbitDB's
      // own per-device id otherwise (non-deterministic, so only its type is kept)
      myAuthorId: c.withIdentity ? log.myAuthorId : typeof log.myAuthorId,
    };
    await log.close();
    return result;
  } finally {
    await libp2p.stop().catch(() => {});
    process.chdir(cwd);
  }
}

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/chat.git