@vdoninja/ninja-p2p 0.1.4 → 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 (46) hide show
  1. package/.agents/skills/ninja-p2p/SKILL.md +102 -2
  2. package/.claude/skills/ninja-p2p/SKILL.md +209 -130
  3. package/.codex/skills/ninja-p2p/SKILL.md +102 -2
  4. package/CHANGELOG.md +113 -0
  5. package/README.md +1077 -775
  6. package/dashboard.html +370 -50
  7. package/dist/agent-state.js +9 -0
  8. package/dist/cli-lib.d.ts +40 -0
  9. package/dist/cli-lib.js +165 -2
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +501 -10
  12. package/dist/demo.d.ts +37 -0
  13. package/dist/demo.js +186 -0
  14. package/dist/doctor.d.ts +52 -0
  15. package/dist/doctor.js +219 -0
  16. package/dist/file-transfer.d.ts +15 -2
  17. package/dist/file-transfer.js +249 -15
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/message-bus.d.ts +15 -0
  21. package/dist/message-bus.js +32 -3
  22. package/dist/peer-registry.js +7 -0
  23. package/dist/protocol.d.ts +78 -1
  24. package/dist/protocol.js +42 -6
  25. package/dist/shared-folders.js +20 -1
  26. package/dist/social-stream.d.ts +100 -0
  27. package/dist/social-stream.js +254 -0
  28. package/dist/swarm-manager.d.ts +164 -0
  29. package/dist/swarm-manager.js +957 -0
  30. package/dist/swarm-session.d.ts +197 -0
  31. package/dist/swarm-session.js +465 -0
  32. package/dist/swarm-wire.d.ts +48 -0
  33. package/dist/swarm-wire.js +86 -0
  34. package/dist/swarm.d.ts +258 -0
  35. package/dist/swarm.js +694 -0
  36. package/dist/vdo-bridge.d.ts +62 -1
  37. package/dist/vdo-bridge.js +273 -23
  38. package/dist/vdoninja-sdk-types.d.ts +75 -0
  39. package/dist/vdoninja-sdk-types.js +10 -0
  40. package/dist/wake.d.ts +83 -0
  41. package/dist/wake.js +206 -0
  42. package/docs/protocol-and-reliability.md +231 -38
  43. package/docs/sdk-wishlist.md +236 -0
  44. package/docs/security.md +197 -0
  45. package/docs/social-stream-bridge.md +390 -0
  46. package/package.json +125 -116
package/dist/demo.js ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Live Self-Test
3
+ *
4
+ * Getting two agents talking used to take six commands and a room-name
5
+ * copy-paste, which is where most people gave up. `ninja-p2p demo` does the
6
+ * whole round trip in one command: two peers connect, find each other over
7
+ * WebRTC, exchange messages both ways, and complete a request/response.
8
+ *
9
+ * It doubles as a diagnostic. If this passes, the transport works on this
10
+ * machine and network, and any remaining problem is configuration.
11
+ */
12
+ import { VDOBridge } from "./vdo-bridge.js";
13
+ import { generateRoomName } from "./protocol.js";
14
+ export const DEMO_ALICE = "demo_alice";
15
+ export const DEMO_BOB = "demo_bob";
16
+ export async function runDemo(options = {}) {
17
+ const room = options.room || generateRoomName();
18
+ const timeoutMs = options.timeoutMs ?? 20_000;
19
+ const password = options.password ?? false;
20
+ const log = options.log ?? (() => { });
21
+ const steps = [];
22
+ const alice = new VDOBridge({
23
+ room,
24
+ streamId: DEMO_ALICE,
25
+ identity: { streamId: DEMO_ALICE, role: "agent", name: "Alice" },
26
+ password,
27
+ skills: ["chat", "command"],
28
+ topics: ["events"],
29
+ });
30
+ const bob = new VDOBridge({
31
+ room,
32
+ streamId: DEMO_BOB,
33
+ identity: { streamId: DEMO_BOB, role: "agent", name: "Bob" },
34
+ password,
35
+ skills: ["chat", "command"],
36
+ topics: ["events"],
37
+ });
38
+ // Bob answers the demo command himself so the request/response leg is a real
39
+ // round trip rather than a message we quietly resolve locally.
40
+ bob.bus.on("message:command", (envelope) => {
41
+ const payload = envelope.payload;
42
+ if (payload.command === "ping") {
43
+ bob.commandResponse(envelope, { pong: true, from: DEMO_BOB });
44
+ }
45
+ });
46
+ const plan = [
47
+ {
48
+ name: "connect",
49
+ run: async () => {
50
+ await Promise.all([alice.connect(), bob.connect()]);
51
+ return `both peers joined room ${room}`;
52
+ },
53
+ },
54
+ {
55
+ name: "discover",
56
+ run: async () => {
57
+ await Promise.all([
58
+ waitForPeer(alice, DEMO_BOB, timeoutMs),
59
+ waitForPeer(bob, DEMO_ALICE, timeoutMs),
60
+ ]);
61
+ return "peers found each other over WebRTC";
62
+ },
63
+ },
64
+ {
65
+ name: "direct message",
66
+ run: async () => {
67
+ const received = waitForMessage(bob, "message:chat", DEMO_ALICE, timeoutMs);
68
+ alice.chat("hello from Alice", DEMO_BOB);
69
+ return `Bob received "${textOf(await received)}"`;
70
+ },
71
+ },
72
+ {
73
+ name: "reply",
74
+ run: async () => {
75
+ const received = waitForMessage(alice, "message:chat", DEMO_BOB, timeoutMs);
76
+ bob.chat("hello back from Bob", DEMO_ALICE);
77
+ return `Alice received "${textOf(await received)}"`;
78
+ },
79
+ },
80
+ {
81
+ name: "command",
82
+ run: async () => {
83
+ const received = waitForMessage(alice, "message:command_response", DEMO_BOB, timeoutMs);
84
+ alice.command(DEMO_BOB, "ping");
85
+ const envelope = await received;
86
+ const payload = envelope.payload;
87
+ return `Bob answered ${JSON.stringify(payload.result ?? envelope.payload)}`;
88
+ },
89
+ },
90
+ ];
91
+ try {
92
+ for (const item of plan) {
93
+ const startedAt = Date.now();
94
+ try {
95
+ const detail = await item.run();
96
+ steps.push({ name: item.name, ok: true, detail, ms: Date.now() - startedAt });
97
+ log(` ok ${item.name.padEnd(15)} ${detail}`);
98
+ }
99
+ catch (error) {
100
+ steps.push({
101
+ name: item.name,
102
+ ok: false,
103
+ detail: errorMessage(error),
104
+ ms: Date.now() - startedAt,
105
+ });
106
+ log(` FAIL ${item.name.padEnd(15)} ${errorMessage(error)}`);
107
+ // Later steps assume the earlier ones worked, so stop at the first break.
108
+ break;
109
+ }
110
+ }
111
+ if (options.hold && steps.length === plan.length && steps.every((step) => step.ok)) {
112
+ await options.hold(room);
113
+ }
114
+ }
115
+ finally {
116
+ await Promise.allSettled([alice.disconnect(), bob.disconnect()]);
117
+ }
118
+ return {
119
+ room,
120
+ ok: steps.length === plan.length && steps.every((step) => step.ok),
121
+ steps,
122
+ };
123
+ }
124
+ export function formatDemoResult(result, dashboardUrl) {
125
+ const lines = ["", result.ok ? "Demo passed." : "Demo failed."];
126
+ if (result.ok) {
127
+ lines.push("", "Two peers connected, found each other, and exchanged messages both ways", "with no server of your own. The same transport backs the agent sidecars.", "", "Watch a room like this in the browser:", ` ${dashboardUrl}`, "", "Start real agents in one room:", " ninja-p2p start --id claude", " ninja-p2p room --id claude", " ninja-p2p start --room <that-room> --id codex", "", "Let them act on their own:", " ninja-p2p start --id claude --on-message \"claude -p 'Check your ninja-p2p inbox'\"");
128
+ }
129
+ else {
130
+ const failed = result.steps.find((step) => !step.ok);
131
+ lines.push("", `First failure: ${failed?.name ?? "unknown"} — ${failed?.detail ?? "no detail"}`, "", "Run `ninja-p2p doctor` to check Node, WebRTC, and signaling reachability.");
132
+ }
133
+ return lines.join("\n");
134
+ }
135
+ function waitForPeer(bridge, streamId, timeoutMs) {
136
+ return new Promise((resolve, reject) => {
137
+ if (bridge.peers.getPeer(streamId)?.identity) {
138
+ resolve();
139
+ return;
140
+ }
141
+ const cleanup = () => {
142
+ clearTimeout(timer);
143
+ bridge.off("peer:announce", onAnnounce);
144
+ };
145
+ const timer = setTimeout(() => {
146
+ cleanup();
147
+ reject(new Error(`timed out after ${timeoutMs}ms waiting for ${streamId}`));
148
+ }, timeoutMs);
149
+ const onAnnounce = (payload) => {
150
+ if (payload.streamId !== streamId)
151
+ return;
152
+ cleanup();
153
+ resolve();
154
+ };
155
+ bridge.on("peer:announce", onAnnounce);
156
+ });
157
+ }
158
+ function waitForMessage(bridge, event, fromStreamId, timeoutMs) {
159
+ return new Promise((resolve, reject) => {
160
+ const cleanup = () => {
161
+ clearTimeout(timer);
162
+ bridge.bus.off(event, onMessage);
163
+ };
164
+ const timer = setTimeout(() => {
165
+ cleanup();
166
+ reject(new Error(`timed out after ${timeoutMs}ms waiting for ${event} from ${fromStreamId}`));
167
+ }, timeoutMs);
168
+ const onMessage = (envelope) => {
169
+ if (envelope.from.streamId !== fromStreamId)
170
+ return;
171
+ cleanup();
172
+ resolve(envelope);
173
+ };
174
+ bridge.bus.on(event, onMessage);
175
+ });
176
+ }
177
+ function textOf(envelope) {
178
+ const payload = envelope.payload;
179
+ if (typeof payload !== "object" || payload === null)
180
+ return "";
181
+ const text = payload.text;
182
+ return typeof text === "string" ? text : "";
183
+ }
184
+ function errorMessage(error) {
185
+ return error instanceof Error ? error.message : String(error);
186
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Diagnostics
3
+ *
4
+ * When a P2P connection fails, the user has no idea whether the problem is the
5
+ * native WebRTC module, the network, the room name, or a sidecar that quietly
6
+ * died. `ninja-p2p doctor` answers that in one command so "it does not connect"
7
+ * becomes an actionable report instead of a support thread.
8
+ */
9
+ export type CheckStatus = "ok" | "warn" | "fail";
10
+ export type DiagnosticCheck = {
11
+ name: string;
12
+ status: CheckStatus;
13
+ detail: string;
14
+ hint?: string;
15
+ };
16
+ export type DiagnosticsReport = {
17
+ ok: boolean;
18
+ checks: DiagnosticCheck[];
19
+ };
20
+ export declare const MINIMUM_NODE_MAJOR = 20;
21
+ export declare const DEFAULT_SIGNALING_URL = "wss://wss.vdo.ninja";
22
+ /** Node 20 is the documented floor; below that the ESM + native combo breaks. */
23
+ export declare function checkNodeVersion(version: string): DiagnosticCheck;
24
+ /**
25
+ * The native WebRTC module is optional for the library but required for a Node
26
+ * sidecar to actually hold a data channel. `doctor` diagnoses the Node sidecar,
27
+ * so reporting success without it sends users toward network debugging when
28
+ * the process cannot possibly connect.
29
+ */
30
+ export declare function checkWebRtc(load?: () => Promise<unknown>): Promise<DiagnosticCheck>;
31
+ /** Confirm the VDO.Ninja signaling server is reachable before blaming NAT. */
32
+ export declare function checkSignaling(url?: string, timeoutMs?: number): Promise<DiagnosticCheck>;
33
+ /** A sidecar that cannot write its state folder will fail in confusing ways. */
34
+ export declare function checkStateRoot(stateRoot: string): DiagnosticCheck;
35
+ export type SidecarSnapshot = {
36
+ id: string;
37
+ room: string | null;
38
+ pid: number | null;
39
+ running: boolean;
40
+ };
41
+ /** Report sidecars this machine believes it started, and whether they survived. */
42
+ export declare function checkSidecars(stateRoot: string, readSession: (dir: string) => {
43
+ room?: string;
44
+ streamId?: string;
45
+ pid?: number;
46
+ } | null, isRunning: (pid: number) => boolean): {
47
+ check: DiagnosticCheck;
48
+ sidecars: SidecarSnapshot[];
49
+ };
50
+ /** A report fails only on hard failures; warnings are survivable. */
51
+ export declare function summarizeChecks(checks: DiagnosticCheck[]): DiagnosticsReport;
52
+ export declare function formatReport(report: DiagnosticsReport, sidecars: SidecarSnapshot[]): string;
package/dist/doctor.js ADDED
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Diagnostics
3
+ *
4
+ * When a P2P connection fails, the user has no idea whether the problem is the
5
+ * native WebRTC module, the network, the room name, or a sidecar that quietly
6
+ * died. `ninja-p2p doctor` answers that in one command so "it does not connect"
7
+ * becomes an actionable report instead of a support thread.
8
+ */
9
+ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
10
+ import path from "node:path";
11
+ export const MINIMUM_NODE_MAJOR = 20;
12
+ export const DEFAULT_SIGNALING_URL = "wss://wss.vdo.ninja";
13
+ const OPTIONAL_WEBRTC_MODULE = "@roamhq/wrtc";
14
+ /** Node 20 is the documented floor; below that the ESM + native combo breaks. */
15
+ export function checkNodeVersion(version) {
16
+ const major = Number.parseInt(version.replace(/^v/, "").split(".")[0] ?? "", 10);
17
+ if (!Number.isFinite(major)) {
18
+ return {
19
+ name: "node",
20
+ status: "warn",
21
+ detail: `could not parse Node version "${version}"`,
22
+ };
23
+ }
24
+ if (major < MINIMUM_NODE_MAJOR) {
25
+ return {
26
+ name: "node",
27
+ status: "fail",
28
+ detail: `Node ${version} is below the required v${MINIMUM_NODE_MAJOR}`,
29
+ hint: `install Node ${MINIMUM_NODE_MAJOR} or newer`,
30
+ };
31
+ }
32
+ return { name: "node", status: "ok", detail: `Node ${version}` };
33
+ }
34
+ /**
35
+ * The native WebRTC module is optional for the library but required for a Node
36
+ * sidecar to actually hold a data channel. `doctor` diagnoses the Node sidecar,
37
+ * so reporting success without it sends users toward network debugging when
38
+ * the process cannot possibly connect.
39
+ */
40
+ export async function checkWebRtc(load = () => import(OPTIONAL_WEBRTC_MODULE)) {
41
+ try {
42
+ await load();
43
+ return { name: "webrtc", status: "ok", detail: "@roamhq/wrtc is installed" };
44
+ }
45
+ catch (error) {
46
+ return {
47
+ name: "webrtc",
48
+ status: "fail",
49
+ detail: `@roamhq/wrtc not loadable: ${errorMessage(error)}`,
50
+ hint: "npm install -g @roamhq/wrtc (Node sidecars need it to hold data channels)",
51
+ };
52
+ }
53
+ }
54
+ /** Confirm the VDO.Ninja signaling server is reachable before blaming NAT. */
55
+ export async function checkSignaling(url = DEFAULT_SIGNALING_URL, timeoutMs = 8000) {
56
+ const startedAt = Date.now();
57
+ let SocketImpl = globalThis.WebSocket;
58
+ if (!SocketImpl) {
59
+ try {
60
+ const ws = await import("ws");
61
+ SocketImpl = (ws.default ?? ws);
62
+ }
63
+ catch (error) {
64
+ return {
65
+ name: "signaling",
66
+ status: "warn",
67
+ detail: `no WebSocket implementation available: ${errorMessage(error)}`,
68
+ hint: "use Node 22+, which ships a global WebSocket",
69
+ };
70
+ }
71
+ }
72
+ return new Promise((resolve) => {
73
+ let settled = false;
74
+ let socket;
75
+ const finish = (check) => {
76
+ if (settled)
77
+ return;
78
+ settled = true;
79
+ clearTimeout(timer);
80
+ try {
81
+ socket?.close();
82
+ }
83
+ catch {
84
+ // The socket may already be closed; nothing useful to do here.
85
+ }
86
+ resolve(check);
87
+ };
88
+ const timer = setTimeout(() => {
89
+ finish({
90
+ name: "signaling",
91
+ status: "fail",
92
+ detail: `no response from ${url} within ${timeoutMs}ms`,
93
+ hint: "check your firewall or proxy; outbound wss:// must be allowed",
94
+ });
95
+ }, timeoutMs);
96
+ try {
97
+ socket = new SocketImpl(url);
98
+ }
99
+ catch (error) {
100
+ finish({
101
+ name: "signaling",
102
+ status: "fail",
103
+ detail: `could not open ${url}: ${errorMessage(error)}`,
104
+ });
105
+ return;
106
+ }
107
+ socket.onopen = () => {
108
+ finish({
109
+ name: "signaling",
110
+ status: "ok",
111
+ detail: `${url} reachable in ${Date.now() - startedAt}ms`,
112
+ });
113
+ };
114
+ socket.onerror = () => {
115
+ finish({
116
+ name: "signaling",
117
+ status: "fail",
118
+ detail: `could not reach ${url}`,
119
+ hint: "check your firewall or proxy; outbound wss:// must be allowed",
120
+ });
121
+ };
122
+ });
123
+ }
124
+ /** A sidecar that cannot write its state folder will fail in confusing ways. */
125
+ export function checkStateRoot(stateRoot) {
126
+ const probe = path.join(stateRoot, ".doctor-write-probe");
127
+ try {
128
+ mkdirSync(stateRoot, { recursive: true });
129
+ writeFileSync(probe, "ok", "utf8");
130
+ rmSync(probe, { force: true });
131
+ return { name: "state", status: "ok", detail: `${stateRoot} is writable` };
132
+ }
133
+ catch (error) {
134
+ return {
135
+ name: "state",
136
+ status: "fail",
137
+ detail: `cannot write ${stateRoot}: ${errorMessage(error)}`,
138
+ hint: "set NINJA_STATE_DIR to a writable folder",
139
+ };
140
+ }
141
+ }
142
+ /** Report sidecars this machine believes it started, and whether they survived. */
143
+ export function checkSidecars(stateRoot, readSession, isRunning) {
144
+ if (!existsSync(stateRoot)) {
145
+ return {
146
+ check: { name: "sidecars", status: "ok", detail: "no sidecars have been started yet" },
147
+ sidecars: [],
148
+ };
149
+ }
150
+ const sidecars = [];
151
+ for (const entry of safeReadDir(stateRoot)) {
152
+ const session = readSession(path.join(stateRoot, entry));
153
+ if (!session)
154
+ continue;
155
+ const pid = typeof session.pid === "number" ? session.pid : null;
156
+ sidecars.push({
157
+ id: session.streamId ?? entry,
158
+ room: session.room ?? null,
159
+ pid,
160
+ running: pid !== null && isRunning(pid),
161
+ });
162
+ }
163
+ const live = sidecars.filter((sidecar) => sidecar.running);
164
+ if (sidecars.length === 0) {
165
+ return {
166
+ check: { name: "sidecars", status: "ok", detail: "no sidecars have been started yet" },
167
+ sidecars,
168
+ };
169
+ }
170
+ const stale = sidecars.length - live.length;
171
+ return {
172
+ check: {
173
+ name: "sidecars",
174
+ status: live.length > 0 || stale === 0 ? "ok" : "warn",
175
+ detail: `${live.length} running, ${stale} stopped`,
176
+ hint: stale > 0 && live.length === 0 ? "start one with: ninja-p2p start --id <name>" : undefined,
177
+ },
178
+ sidecars,
179
+ };
180
+ }
181
+ /** A report fails only on hard failures; warnings are survivable. */
182
+ export function summarizeChecks(checks) {
183
+ return {
184
+ ok: checks.every((check) => check.status !== "fail"),
185
+ checks,
186
+ };
187
+ }
188
+ export function formatReport(report, sidecars) {
189
+ const symbols = { ok: "ok ", warn: "warn", fail: "FAIL" };
190
+ const lines = ["ninja-p2p doctor", ""];
191
+ for (const check of report.checks) {
192
+ lines.push(`[${symbols[check.status]}] ${check.name.padEnd(10)} ${check.detail}`);
193
+ if (check.hint) {
194
+ lines.push(`${" ".repeat(18)}-> ${check.hint}`);
195
+ }
196
+ }
197
+ if (sidecars.length > 0) {
198
+ lines.push("", "Sidecars:");
199
+ for (const sidecar of sidecars) {
200
+ const state = sidecar.running ? `running pid=${sidecar.pid}` : "stopped";
201
+ lines.push(` ${sidecar.id.padEnd(16)} ${state} room=${sidecar.room ?? "unknown"}`);
202
+ }
203
+ }
204
+ lines.push("", report.ok ? "All required checks passed." : "Some checks failed; see hints above.");
205
+ return lines.join("\n");
206
+ }
207
+ function safeReadDir(dir) {
208
+ try {
209
+ return readdirSync(dir, { withFileTypes: true })
210
+ .filter((entry) => entry.isDirectory())
211
+ .map((entry) => entry.name);
212
+ }
213
+ catch {
214
+ return [];
215
+ }
216
+ }
217
+ function errorMessage(error) {
218
+ return error instanceof Error ? error.message : String(error);
219
+ }
@@ -1,6 +1,14 @@
1
1
  import { type FileAckPayload, type FileChunkPayload, type FileCompletePayload, type FileOfferPayload, type FileTransferKind, type PeerIdentity } from "./protocol.js";
2
2
  import type { VDOBridge } from "./vdo-bridge.js";
3
3
  export declare const DEFAULT_TRANSFER_CHUNK_SIZE = 12000;
4
+ /** The simple transfer path buffers at the sender; use swarm transfer above this. */
5
+ export declare const MAX_BASIC_TRANSFER_SIZE: number;
6
+ export declare const MAX_BASIC_TRANSFER_CHUNK_SIZE: number;
7
+ export declare const MAX_BASIC_TRANSFER_CHUNKS: number;
8
+ export declare const MAX_INCOMPLETE_TRANSFER_BYTES: number;
9
+ export declare const MAX_INCOMPLETE_TRANSFERS = 16;
10
+ export declare const INCOMPLETE_TRANSFER_STALE_MS: number;
11
+ export declare const TRANSFER_ID_PATTERN: RegExp;
4
12
  export type PreparedFileTransfer = {
5
13
  name: string;
6
14
  filePath: string;
@@ -42,11 +50,16 @@ export declare function prepareFileTransferFromPath(filePath: string, kind?: Fil
42
50
  export declare function sendPreparedFileTransfer(bridge: VDOBridge, targetStreamId: string, prepared: PreparedFileTransfer, chunkSize?: number): FileOfferPayload;
43
51
  export declare function sendFileFromPath(bridge: VDOBridge, targetStreamId: string, filePath: string, kind?: FileTransferKind, chunkSize?: number): FileOfferPayload;
44
52
  export declare function beginIncomingTransfer(stateDir: string, from: PeerIdentity, offer: FileOfferPayload): IncomingTransferManifest;
45
- export declare function appendIncomingTransferChunk(stateDir: string, payload: FileChunkPayload): IncomingTransferManifest;
46
- export declare function completeIncomingTransfer(stateDir: string, payload: FileCompletePayload): CompletedTransferResult;
53
+ export declare function appendIncomingTransferChunk(stateDir: string, payload: FileChunkPayload, fromStreamId?: string): IncomingTransferManifest;
54
+ export declare function completeIncomingTransfer(stateDir: string, payload: FileCompletePayload, fromStreamId?: string): CompletedTransferResult;
47
55
  export declare function createFileAckPayload(result: CompletedTransferResult): FileAckPayload;
48
56
  export declare function createFailedFileAckPayload(transferId: string, error: unknown): FileAckPayload;
49
57
  export declare function readTransferManifest(stateDir: string, transferId: string): IncomingTransferManifest | null;
58
+ /**
59
+ * Remove a transfer that cannot be completed. Sender matching prevents a peer
60
+ * from aborting somebody else's transfer by guessing its id.
61
+ */
62
+ export declare function discardIncomingTransfer(stateDir: string, transferId: string, expectedSender?: string): boolean;
50
63
  export declare function sha256Hex(bytes: Uint8Array): string;
51
64
  export declare function bytesToBase64(bytes: Uint8Array): string;
52
65
  export declare function base64ToBytes(value: string): Uint8Array;