anchordb-relay 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -0
  3. package/dist/cjs/cli.d.ts +3 -0
  4. package/dist/cjs/cli.d.ts.map +1 -0
  5. package/dist/cjs/cli.js +57 -0
  6. package/dist/cjs/cli.js.map +1 -0
  7. package/dist/cjs/index.d.ts +7 -0
  8. package/dist/cjs/index.d.ts.map +1 -0
  9. package/dist/cjs/index.js +10 -0
  10. package/dist/cjs/index.js.map +1 -0
  11. package/dist/cjs/mongo-bridge.d.ts +65 -0
  12. package/dist/cjs/mongo-bridge.d.ts.map +1 -0
  13. package/dist/cjs/mongo-bridge.js +289 -0
  14. package/dist/cjs/mongo-bridge.js.map +1 -0
  15. package/dist/cjs/package.json +3 -0
  16. package/dist/cjs/relay.d.ts +78 -0
  17. package/dist/cjs/relay.d.ts.map +1 -0
  18. package/dist/cjs/relay.js +117 -0
  19. package/dist/cjs/relay.js.map +1 -0
  20. package/dist/cjs/server.d.ts +30 -0
  21. package/dist/cjs/server.d.ts.map +1 -0
  22. package/dist/cjs/server.js +97 -0
  23. package/dist/cjs/server.js.map +1 -0
  24. package/dist/esm/cli.d.ts +3 -0
  25. package/dist/esm/cli.d.ts.map +1 -0
  26. package/dist/esm/cli.js +55 -0
  27. package/dist/esm/cli.js.map +1 -0
  28. package/dist/esm/index.d.ts +7 -0
  29. package/dist/esm/index.d.ts.map +1 -0
  30. package/dist/esm/index.js +4 -0
  31. package/dist/esm/index.js.map +1 -0
  32. package/dist/esm/mongo-bridge.d.ts +65 -0
  33. package/dist/esm/mongo-bridge.d.ts.map +1 -0
  34. package/dist/esm/mongo-bridge.js +252 -0
  35. package/dist/esm/mongo-bridge.js.map +1 -0
  36. package/dist/esm/package.json +3 -0
  37. package/dist/esm/relay.d.ts +78 -0
  38. package/dist/esm/relay.d.ts.map +1 -0
  39. package/dist/esm/relay.js +113 -0
  40. package/dist/esm/relay.js.map +1 -0
  41. package/dist/esm/server.d.ts +30 -0
  42. package/dist/esm/server.d.ts.map +1 -0
  43. package/dist/esm/server.js +61 -0
  44. package/dist/esm/server.js.map +1 -0
  45. package/package.json +65 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The Anchor Inspector Relay — development only.
3
+ *
4
+ * ## Why a relay exists at all
5
+ *
6
+ * React Native (and every browser) can open a WebSocket **client** but cannot **listen**. Neither
7
+ * the app being inspected nor Anchor Lens can host a server without a native TCP module, which
8
+ * would force an Expo dev build and rule out Expo Go entirely.
9
+ *
10
+ * So both sides dial out to this tiny Node process, which pairs them by room and copies frames
11
+ * across. Nothing here understands the inspector protocol — it is a dumb pipe on purpose, so the
12
+ * protocol can evolve without the relay ever needing an update.
13
+ *
14
+ * ## What it deliberately does NOT do
15
+ *
16
+ * It does not authenticate the inspector session. Pairing is end-to-end between the agent and Lens
17
+ * (HMAC over a challenge), so a compromised or malicious relay still cannot issue itself a session.
18
+ * The room token here only decides *who can be connected to whom*; it is not the security boundary.
19
+ */
20
+ export type RelayRole = "agent" | "client";
21
+ export interface RelaySocket {
22
+ send(data: string): void;
23
+ close(code?: number, reason?: string): void;
24
+ on(event: "message", handler: (data: unknown) => void): void;
25
+ on(event: "close", handler: () => void): void;
26
+ on(event: "error", handler: (err: Error) => void): void;
27
+ }
28
+ export interface RelayOptions {
29
+ /** Rooms with no agent for this long are reclaimed. */
30
+ roomTtlMs?: number;
31
+ /** Frames larger than this are refused rather than forwarded. */
32
+ maxFrameBytes?: number;
33
+ onLog?: (message: string) => void;
34
+ }
35
+ export interface RoomInfo {
36
+ id: string;
37
+ agentName: string;
38
+ agentConnected: boolean;
39
+ clientConnected: boolean;
40
+ ageMs: number;
41
+ }
42
+ /**
43
+ * Transport-agnostic relay core.
44
+ *
45
+ * Kept free of any `ws` import so it can be driven by a real WebSocket server, by a test double, or
46
+ * by any other transport, and so the routing logic is testable without opening a port.
47
+ */
48
+ export declare class InspectorRelay {
49
+ private readonly rooms;
50
+ private readonly opts;
51
+ constructor(options?: RelayOptions);
52
+ /** Create a room for an app to publish itself into. The id goes into the QR code. */
53
+ createRoom(agentName?: string): string;
54
+ /** Does this room exist? The bridge uses it as an access token. */
55
+ hasRoom(id: string): boolean;
56
+ rooms_(): RoomInfo[];
57
+ /**
58
+ * Join a socket to a room in a given role.
59
+ *
60
+ * A second socket claiming an occupied role is refused rather than replacing the incumbent:
61
+ * silently swapping would let anyone who learns a room id evict the real Lens mid-session.
62
+ */
63
+ join(roomId: string, role: RelayRole, socket: RelaySocket): {
64
+ ok: true;
65
+ } | {
66
+ ok: false;
67
+ reason: string;
68
+ };
69
+ closeRoom(roomId: string): void;
70
+ private reap;
71
+ /** Connection details a QR code encodes. The pairing code is NOT included — see below. */
72
+ connectionInfo(host: string, port: number, roomId: string): {
73
+ url: string;
74
+ room: string;
75
+ deepLink: string;
76
+ };
77
+ }
78
+ //# sourceMappingURL=relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/relay.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE3C,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IAC7D,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC9C,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;CACzD;AAUD,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyE;gBAElF,OAAO,GAAE,YAAiB;IAQtC,qFAAqF;IACrF,UAAU,CAAC,SAAS,SAAa,GAAG,MAAM;IAQ1C,mEAAmE;IACnE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAK5B,MAAM,IAAI,QAAQ,EAAE;IAWpB;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAqCxG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQ/B,OAAO,CAAC,IAAI;IAQZ,0FAA0F;IAC1F,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;QAC1D,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB;CAaF"}
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InspectorRelay = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ /**
6
+ * Transport-agnostic relay core.
7
+ *
8
+ * Kept free of any `ws` import so it can be driven by a real WebSocket server, by a test double, or
9
+ * by any other transport, and so the routing logic is testable without opening a port.
10
+ */
11
+ class InspectorRelay {
12
+ constructor(options = {}) {
13
+ this.rooms = new Map();
14
+ this.opts = {
15
+ roomTtlMs: options.roomTtlMs ?? 30 * 60_000,
16
+ maxFrameBytes: options.maxFrameBytes ?? 4 * 1024 * 1024,
17
+ onLog: options.onLog ?? (() => undefined),
18
+ };
19
+ }
20
+ /** Create a room for an app to publish itself into. The id goes into the QR code. */
21
+ createRoom(agentName = "AnchorDB") {
22
+ this.reap();
23
+ const id = (0, node_crypto_1.randomBytes)(9).toString("base64url");
24
+ this.rooms.set(id, { id, agent: null, client: null, createdAt: Date.now(), agentName });
25
+ this.opts.onLog(`room ${id} created for "${agentName}"`);
26
+ return id;
27
+ }
28
+ /** Does this room exist? The bridge uses it as an access token. */
29
+ hasRoom(id) {
30
+ this.reap();
31
+ return this.rooms.has(id);
32
+ }
33
+ rooms_() {
34
+ this.reap();
35
+ return [...this.rooms.values()].map((r) => ({
36
+ id: r.id,
37
+ agentName: r.agentName,
38
+ agentConnected: r.agent !== null,
39
+ clientConnected: r.client !== null,
40
+ ageMs: Date.now() - r.createdAt,
41
+ }));
42
+ }
43
+ /**
44
+ * Join a socket to a room in a given role.
45
+ *
46
+ * A second socket claiming an occupied role is refused rather than replacing the incumbent:
47
+ * silently swapping would let anyone who learns a room id evict the real Lens mid-session.
48
+ */
49
+ join(roomId, role, socket) {
50
+ this.reap();
51
+ const room = this.rooms.get(roomId);
52
+ if (!room)
53
+ return { ok: false, reason: "unknown room" };
54
+ if (room[role])
55
+ return { ok: false, reason: `a ${role} is already connected to this room` };
56
+ room[role] = socket;
57
+ this.opts.onLog(`room ${roomId}: ${role} joined`);
58
+ socket.on("message", (data) => {
59
+ const frame = typeof data === "string" ? data : String(data);
60
+ if (frame.length > this.opts.maxFrameBytes) {
61
+ this.opts.onLog(`room ${roomId}: frame of ${frame.length} bytes refused`);
62
+ return;
63
+ }
64
+ const peer = role === "agent" ? room.client : room.agent;
65
+ // Frames are forwarded verbatim. The relay never parses the protocol.
66
+ if (peer)
67
+ peer.send(frame);
68
+ });
69
+ socket.on("close", () => {
70
+ if (room[role] === socket)
71
+ room[role] = null;
72
+ this.opts.onLog(`room ${roomId}: ${role} left`);
73
+ // Closing the agent ends the session for the client too — there is nothing left to inspect.
74
+ if (role === "agent" && room.client) {
75
+ room.client.close(4001, "agent disconnected");
76
+ room.client = null;
77
+ }
78
+ });
79
+ socket.on("error", () => {
80
+ if (room[role] === socket)
81
+ room[role] = null;
82
+ });
83
+ return { ok: true };
84
+ }
85
+ closeRoom(roomId) {
86
+ const room = this.rooms.get(roomId);
87
+ if (!room)
88
+ return;
89
+ room.agent?.close(4000, "room closed");
90
+ room.client?.close(4000, "room closed");
91
+ this.rooms.delete(roomId);
92
+ }
93
+ reap() {
94
+ const now = Date.now();
95
+ for (const [id, room] of this.rooms) {
96
+ const stale = now - room.createdAt > this.opts.roomTtlMs;
97
+ if (stale && !room.agent && !room.client)
98
+ this.rooms.delete(id);
99
+ }
100
+ }
101
+ /** Connection details a QR code encodes. The pairing code is NOT included — see below. */
102
+ connectionInfo(host, port, roomId) {
103
+ const url = `ws://${host}:${port}/relay?room=${roomId}&role=client`;
104
+ return {
105
+ url,
106
+ room: roomId,
107
+ /**
108
+ * The QR carries the relay address and room only. The pairing code is displayed separately
109
+ * and typed by the developer, so photographing the screen is not enough to pair — which is
110
+ * the whole point of keeping the code out of the QR (§51).
111
+ */
112
+ deepLink: `anchor-lens://connect?relay=${encodeURIComponent(url)}&room=${roomId}`,
113
+ };
114
+ }
115
+ }
116
+ exports.InspectorRelay = InspectorRelay;
117
+ //# sourceMappingURL=relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.js","sourceRoot":"","sources":["../../src/relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { InspectorRelay, type RelayOptions } from "./relay.js";
2
+ import { MongoBridge, type MongoDriver } from "./mongo-bridge.js";
3
+ /**
4
+ * A runnable relay over `ws`.
5
+ *
6
+ * `ws` is loaded lazily so `InspectorRelay` itself stays dependency-free and testable without a
7
+ * socket library — the routing logic is the part worth testing, and it should not require a port.
8
+ */
9
+ export interface RelayServerOptions extends RelayOptions {
10
+ port?: number;
11
+ host?: string;
12
+ /**
13
+ * Enable the MongoDB bridge on `/bridge`.
14
+ *
15
+ * Off by default and deliberately so: a relay that will dial any MongoDB on request lets anything
16
+ * that can reach the port use this machine to reach a database.
17
+ */
18
+ mongoBridge?: boolean;
19
+ /** Supply a driver instead of importing `mongodb` — used by the tests. */
20
+ mongoDriver?: () => Promise<MongoDriver>;
21
+ }
22
+ export interface RelayServer {
23
+ relay: InspectorRelay;
24
+ bridge: MongoBridge;
25
+ port: number;
26
+ host: string;
27
+ close(): Promise<void>;
28
+ }
29
+ export declare function startRelayServer(options?: RelayServerOptions): Promise<RelayServer>;
30
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAoB,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElE;;;;;GAKG;AACH,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CAoE7F"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.startRelayServer = startRelayServer;
37
+ const node_http_1 = require("node:http");
38
+ const relay_ts_1 = require("./relay.js");
39
+ const mongo_bridge_ts_1 = require("./mongo-bridge.js");
40
+ async function startRelayServer(options = {}) {
41
+ const port = options.port ?? 9440;
42
+ const host = options.host ?? "127.0.0.1";
43
+ const relay = new relay_ts_1.InspectorRelay(options);
44
+ const bridge = new mongo_bridge_ts_1.MongoBridge({
45
+ enabled: options.mongoBridge ?? false,
46
+ ...(options.mongoDriver ? { driver: options.mongoDriver } : {}),
47
+ ...(options.onLog ? { onLog: options.onLog } : {}),
48
+ });
49
+ const { WebSocketServer } = (await Promise.resolve().then(() => __importStar(require("ws"))));
50
+ const http = (0, node_http_1.createServer)((req, res) => {
51
+ // A tiny control surface: create a room, list rooms, health.
52
+ if (req.url?.startsWith("/rooms") && req.method === "POST") {
53
+ const id = relay.createRoom("AnchorDB");
54
+ res.writeHead(200, { "content-type": "application/json" });
55
+ // connectionInfo already carries `room`; spreading after it would silently overwrite.
56
+ res.end(JSON.stringify(relay.connectionInfo(host, port, id)));
57
+ return;
58
+ }
59
+ if (req.url?.startsWith("/rooms") && req.method === "GET") {
60
+ res.writeHead(200, { "content-type": "application/json" });
61
+ res.end(JSON.stringify(relay.rooms_()));
62
+ return;
63
+ }
64
+ res.writeHead(200, { "content-type": "application/json" });
65
+ res.end(JSON.stringify({ ok: true, service: "anchor-inspector-relay" }));
66
+ });
67
+ const wss = new WebSocketServer({ server: http });
68
+ wss.on("connection", (socket, request) => {
69
+ const url = new URL(request.url ?? "/", `http://${host}:${port}`);
70
+ const room = url.searchParams.get("room");
71
+ const role = url.searchParams.get("role");
72
+ // The bridge is a separate endpoint, not a room member: the relay answers it rather than
73
+ // forwarding it to the app.
74
+ if (url.pathname === "/bridge") {
75
+ bridge.join(socket, room, (id) => relay.hasRoom(id));
76
+ return;
77
+ }
78
+ if (!room || (role !== "agent" && role !== "client")) {
79
+ socket.close(4400, "room and role=agent|client are required");
80
+ return;
81
+ }
82
+ const result = relay.join(room, role, socket);
83
+ if (!result.ok)
84
+ socket.close(4404, result.reason);
85
+ });
86
+ await new Promise((resolve) => http.listen(port, host, resolve));
87
+ return {
88
+ relay,
89
+ bridge,
90
+ port,
91
+ host,
92
+ close: () => new Promise((resolve) => {
93
+ wss.close(() => http.close(() => resolve()));
94
+ }),
95
+ };
96
+ }
97
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { startRelayServer } from "./server.js";
3
+ /**
4
+ * `npx anchor-relay`
5
+ *
6
+ * Prints the room and the QR payload a developer scans. Binds to 127.0.0.1 by default: a relay
7
+ * reachable from the whole network is a database reachable from the whole network, so opening it
8
+ * up has to be a deliberate `--host 0.0.0.0`.
9
+ */
10
+ const args = process.argv.slice(2);
11
+ const readFlag = (name, fallback) => {
12
+ const i = args.indexOf(`--${name}`);
13
+ return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
14
+ };
15
+ const readSwitch = (name) => args.includes(`--${name}`);
16
+ const port = Number(readFlag("port", "9440"));
17
+ const host = readFlag("host", "127.0.0.1");
18
+ const mongoBridge = readSwitch("mongo-bridge");
19
+ // Wrapped in a function rather than using top-level await: this file is emitted as both ESM and
20
+ // CommonJS, and top-level await cannot be expressed in CJS at all.
21
+ async function main() {
22
+ const server = await startRelayServer({ port, host, mongoBridge, onLog: (m) => console.log(` ${m}`) });
23
+ const room = server.relay.createRoom("AnchorDB");
24
+ const info = server.relay.connectionInfo(host, port, room);
25
+ console.log("\nAnchor Inspector Relay");
26
+ console.log("──────────────────────");
27
+ console.log(` listening ws://${host}:${port}`);
28
+ console.log(` room ${room}`);
29
+ console.log(` agent URL ws://${host}:${port}/relay?room=${room}&role=agent`);
30
+ console.log(` Lens link ${info.deepLink}`);
31
+ if (mongoBridge) {
32
+ console.log(` MongoDB ws://${host}:${port}/bridge?room=${room}`);
33
+ console.log("\n \x1b[33m! The MongoDB bridge is ON. Anything that can reach this relay and knows the\x1b[0m");
34
+ console.log(" \x1b[33m room can use this machine to reach a database you connect to.\x1b[0m");
35
+ console.log(" Connection strings are never stored or logged — they live only while Lens is connected.");
36
+ }
37
+ else {
38
+ console.log(" MongoDB off — start with --mongo-bridge to sync collections to a real MongoDB");
39
+ }
40
+ if (host !== "127.0.0.1") {
41
+ console.log("\n \x1b[33m! Bound beyond localhost — anyone on this network can reach the relay.\x1b[0m");
42
+ }
43
+ console.log("\n The pairing code is shown by the APP, not here: the QR alone must not be enough to pair.\n");
44
+ const shutdown = async () => {
45
+ await server.close();
46
+ process.exit(0);
47
+ };
48
+ process.on("SIGINT", shutdown);
49
+ process.on("SIGTERM", shutdown);
50
+ }
51
+ main().catch((err) => {
52
+ console.error("anchor-relay failed to start:", err);
53
+ process.exit(1);
54
+ });
55
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { InspectorRelay } from "./relay.js";
2
+ export type { RelayOptions, RelayRole, RelaySocket, RoomInfo } from "./relay.js";
3
+ export { MongoBridge } from "./mongo-bridge.js";
4
+ export type { MongoBridgeOptions, MongoConnection, MongoDriver } from "./mongo-bridge.js";
5
+ export { startRelayServer } from "./server.js";
6
+ export type { RelayServer, RelayServerOptions } from "./server.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { InspectorRelay } from "./relay.js";
2
+ export { MongoBridge } from "./mongo-bridge.js";
3
+ export { startRelayServer } from "./server.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { type BridgeWriteMode, type MongoCollectionInfo } from "anchordb";
2
+ import type { RelaySocket } from "./relay.js";
3
+ /**
4
+ * The MongoDB half of the bridge.
5
+ *
6
+ * Anchor Lens cannot open a MongoDB connection — React Native has no TCP sockets — so the driver
7
+ * runs here, in the relay, and Lens drives it. See `bridge.ts` in `anchordb` for the protocol
8
+ * and the reasoning.
9
+ *
10
+ * ## Three deliberate restrictions
11
+ *
12
+ * 1. **Off by default.** A relay that will dial any MongoDB on request is a confused deputy: every
13
+ * local process could use it to reach a database it has no credentials for. It takes
14
+ * `--mongo-bridge` to turn on.
15
+ * 2. **The room is the token.** A bridge socket must present the room id the relay printed, so
16
+ * knowing the port is not enough.
17
+ * 3. **The connection string is never stored and never logged.** It lives in one `MongoClient` for
18
+ * the life of one socket, and every log line and error message goes through `redactMongoUri`.
19
+ *
20
+ * Even so, this is a development tool on a developer's machine. It is not built to be exposed, and
21
+ * `--host 0.0.0.0` plus `--mongo-bridge` is a combination worth thinking twice about.
22
+ */
23
+ /** The slice of the MongoDB driver this uses, so tests can supply a fake instead of a server. */
24
+ export interface MongoDriver {
25
+ connect(uri: string): Promise<MongoConnection>;
26
+ /** MongoDB's own Extended JSON codec — never a second implementation. */
27
+ stringifyEJSON(value: unknown): string;
28
+ parseEJSON(text: string): unknown;
29
+ }
30
+ export interface MongoConnection {
31
+ serverVersion(): Promise<string>;
32
+ listCollections(database: string): Promise<MongoCollectionInfo[]>;
33
+ find(database: string, collection: string, batchSize: number): AsyncIterable<unknown[]>;
34
+ write(database: string, collection: string, documents: unknown[], mode: BridgeWriteMode): Promise<{
35
+ inserted: number;
36
+ updated: number;
37
+ skipped: number;
38
+ }>;
39
+ close(): Promise<void>;
40
+ }
41
+ export interface MongoBridgeOptions {
42
+ enabled: boolean;
43
+ /** Returns the driver. Defaults to a lazy `import("mongodb")`. */
44
+ driver?: () => Promise<MongoDriver>;
45
+ onLog?: (message: string) => void;
46
+ }
47
+ export declare class MongoBridge {
48
+ private readonly enabled;
49
+ private readonly loadDriver;
50
+ private readonly onLog;
51
+ /** One MongoDB connection per socket, closed with it. */
52
+ private readonly sessions;
53
+ constructor(options: MongoBridgeOptions);
54
+ /** Attach a socket that dialled `/bridge`. */
55
+ join(socket: RelaySocket, room: string | null, validRoom: (room: string) => boolean): void;
56
+ private release;
57
+ private send;
58
+ private handle;
59
+ private connect;
60
+ private session;
61
+ /** Stream a collection out as newline-delimited Extended JSON. */
62
+ private read;
63
+ private write;
64
+ }
65
+ //# sourceMappingURL=mongo-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongo-bridge.d.ts","sourceRoot":"","sources":["../../src/mongo-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,iGAAiG;AACjG,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC/C,yEAAyE;IACzE,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAClE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACxF,KAAK,CACH,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,EAAE,EACpB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAKD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;IACpC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAUD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6E;gBAE1F,OAAO,EAAE,kBAAkB;IAMvC,8CAA8C;IAC9C,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI;YAmB5E,OAAO;IASrB,OAAO,CAAC,IAAI;YAIE,MAAM;YAsCN,OAAO;IAwCrB,OAAO,CAAC,OAAO;IAMf,kEAAkE;YACpD,IAAI;YA0BJ,KAAK;CA4BpB"}