@nicknisi/pi-relay 0.3.4 → 0.4.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.
- package/README.md +6 -2
- package/core.ts +3 -1
- package/discovery.ts +152 -0
- package/dist/core.d.ts +3 -3
- package/dist/core.test.js +23 -2
- package/dist/discovery.d.ts +48 -0
- package/dist/discovery.js +97 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +95 -0
- package/dist/extension.test.d.ts +1 -0
- package/dist/extension.test.js +190 -0
- package/dist/filesystem.js +1 -1
- package/dist/format.d.ts +0 -2
- package/dist/format.js +0 -6
- package/dist/index.js +106 -45
- package/dist/mailbox.d.ts +9 -6
- package/dist/mailbox.js +125 -11
- package/dist/receipts.test.d.ts +1 -0
- package/dist/receipts.test.js +183 -0
- package/dist/registry.js +7 -0
- package/dist/relay.test.js +3 -7
- package/dist/routing.d.ts +18 -0
- package/dist/routing.js +58 -0
- package/filesystem.ts +1 -1
- package/format.ts +0 -8
- package/index.ts +129 -56
- package/mailbox.ts +135 -10
- package/package.json +7 -5
- package/registry.ts +6 -0
- package/routing.ts +70 -0
package/routing.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Letter } from './mailbox.js';
|
|
2
|
+
import type { SessionRecord } from './registry.js';
|
|
3
|
+
|
|
4
|
+
/** Exact session IDs share the target namespace with names and addresses. Never guess collisions. */
|
|
5
|
+
export function resolveSessionTarget(
|
|
6
|
+
to: string,
|
|
7
|
+
records: SessionRecord[],
|
|
8
|
+
selfAddr: string,
|
|
9
|
+
): { record?: SessionRecord; error?: string } {
|
|
10
|
+
const peers = records.filter((r) => r.addr !== selfAddr);
|
|
11
|
+
const exact = peers.filter((r) => r.sessionId === to || r.addr === to || r.name.toLowerCase() === to.toLowerCase());
|
|
12
|
+
const matches = exact.length ? exact : peers.filter((r) => r.addr.startsWith(to));
|
|
13
|
+
const label = (r: SessionRecord) =>
|
|
14
|
+
`${JSON.stringify(r.name)} (address ${r.addr}, sessionId ${JSON.stringify(r.sessionId)})`;
|
|
15
|
+
if (!matches.length)
|
|
16
|
+
return { error: `No session matches ${JSON.stringify(to)}. Use list or list-cwd to discover peers.` };
|
|
17
|
+
if (matches.length > 1)
|
|
18
|
+
return {
|
|
19
|
+
error: `${JSON.stringify(to)} is ambiguous: ${matches.map(label).join(', ')}. Use the intended full address.`,
|
|
20
|
+
};
|
|
21
|
+
return { record: matches[0]! };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ReplyTarget {
|
|
25
|
+
id: string;
|
|
26
|
+
addr: string;
|
|
27
|
+
ask: boolean;
|
|
28
|
+
}
|
|
29
|
+
const ID = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/;
|
|
30
|
+
const ADDRESS = /^[a-f0-9]{12}$/;
|
|
31
|
+
|
|
32
|
+
/** Reconstruct ordinary reply targets from the active branch, not abandoned histories or process-local delivery state. */
|
|
33
|
+
export function resolveReplyTarget(
|
|
34
|
+
reference: string,
|
|
35
|
+
pending: Letter[],
|
|
36
|
+
branch: readonly unknown[],
|
|
37
|
+
): { target?: ReplyTarget; error?: string } {
|
|
38
|
+
if (!ID.test(reference)) return { error: 'replyTo must be a message ID or a nonempty unique prefix.' };
|
|
39
|
+
const candidates: ReplyTarget[] = pending.map((ask) => ({ id: ask.id, addr: ask.from.addr, ask: true }));
|
|
40
|
+
for (const entry of branch) {
|
|
41
|
+
if (typeof entry !== 'object' || entry === null) continue;
|
|
42
|
+
const e = entry as Record<string, unknown>;
|
|
43
|
+
if (e.type !== 'custom_message' || e.customType !== 'relay:delivery') continue;
|
|
44
|
+
const d = e.details;
|
|
45
|
+
if (typeof d !== 'object' || d === null) continue;
|
|
46
|
+
const data = d as Record<string, unknown>;
|
|
47
|
+
if (data.kind !== 'message' && data.kind !== 'reply') continue;
|
|
48
|
+
if (typeof data.id !== 'string' || !ID.test(data.id) || typeof data.from !== 'object' || data.from === null)
|
|
49
|
+
continue;
|
|
50
|
+
const addr = (data.from as Record<string, unknown>).addr;
|
|
51
|
+
if (typeof addr !== 'string' || !ADDRESS.test(addr)) continue;
|
|
52
|
+
candidates.push({ id: data.id, addr, ask: false });
|
|
53
|
+
}
|
|
54
|
+
const matches = new Map<string, ReplyTarget>();
|
|
55
|
+
const exact = candidates.filter((candidate) => candidate.id === reference);
|
|
56
|
+
for (const candidate of exact.length ? exact : candidates) {
|
|
57
|
+
if (!candidate.id.startsWith(reference)) continue;
|
|
58
|
+
const key = `${candidate.id}\0${candidate.addr}`;
|
|
59
|
+
matches.set(key, { ...candidate, ask: candidate.ask || matches.get(key)?.ask === true });
|
|
60
|
+
}
|
|
61
|
+
if (!matches.size)
|
|
62
|
+
return {
|
|
63
|
+
error: `No received message or pending ask matches ${JSON.stringify(reference)} in the active conversation. Use its full ID or list pending asks.`,
|
|
64
|
+
};
|
|
65
|
+
if (matches.size !== 1)
|
|
66
|
+
return {
|
|
67
|
+
error: `Reply ID ${JSON.stringify(reference)} is ambiguous. Use a longer prefix or full ID. Sender addresses: ${[...new Set([...matches.values()].map((m) => m.addr))].join(', ')}.`,
|
|
68
|
+
};
|
|
69
|
+
return { target: matches.values().next().value! };
|
|
70
|
+
}
|