@openlv/signaling 0.0.2 → 0.1.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/dist/gundb/index.js +4 -3
- package/dist/gundb/index.js.map +1 -1
- package/dist/handshake.test.d.ts +1 -0
- package/dist/handshake.test.js +226 -0
- package/dist/handshake.test.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +175 -99
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +4 -0
- package/dist/index.test.js.map +1 -1
- package/dist/messages.d.ts +37 -7
- package/dist/messages.js +111 -1
- package/dist/messages.js.map +1 -1
- package/dist/messages.test.d.ts +1 -0
- package/dist/messages.test.js +92 -0
- package/dist/messages.test.js.map +1 -0
- package/dist/mqtt/index.js +6 -9
- package/dist/mqtt/index.js.map +1 -1
- package/dist/ntfy/index.js +23 -21
- package/dist/ntfy/index.js.map +1 -1
- package/dist/utils/log.d.ts +1 -1
- package/dist/utils/log.js +2 -1
- package/dist/utils/log.js.map +1 -1
- package/package.json +4 -10
package/dist/gundb/index.js
CHANGED
|
@@ -6,14 +6,15 @@ import { log } from "../utils/log.js";
|
|
|
6
6
|
* GunDB Signaling Layer
|
|
7
7
|
* https://openlv.sh/api/signaling/gun
|
|
8
8
|
*/
|
|
9
|
-
export const gundb = ({ topic, url
|
|
9
|
+
export const gundb = ({ topic, url }) => {
|
|
10
|
+
const endpoint = url || "wss://try.axe.eco/gun";
|
|
10
11
|
let connection;
|
|
11
12
|
return createSignalingLayer({
|
|
12
13
|
type: "gundb",
|
|
13
14
|
async setup() {
|
|
14
15
|
log("GUNDB: Setting up");
|
|
15
16
|
const x = new Gun({
|
|
16
|
-
peers: [
|
|
17
|
+
peers: [endpoint],
|
|
17
18
|
file: undefined,
|
|
18
19
|
localStorage: undefined,
|
|
19
20
|
});
|
|
@@ -21,7 +22,7 @@ export const gundb = ({ topic, url = "wss://try.axe.eco/gun", }) => {
|
|
|
21
22
|
await connection?.get(topic);
|
|
22
23
|
},
|
|
23
24
|
teardown() {
|
|
24
|
-
|
|
25
|
+
connection?.get(topic).off();
|
|
25
26
|
connection = undefined;
|
|
26
27
|
},
|
|
27
28
|
async publish(payload) {
|
package/dist/gundb/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gundb/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,GAAG,EAAE,EAAgC,MAAM,KAAK,CAAC;AAExD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gundb/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,GAAG,EAAE,EAAgC,MAAM,KAAK,CAAC;AAExD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAsB,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;IACzD,MAAM,QAAQ,GAAG,GAAG,IAAI,uBAAuB,CAAC;IAChD,IAAI,UAAoC,CAAC;IAEzC,OAAO,oBAAoB,CAAC;QAC1B,IAAI,EAAE,OAAO;QACb,KAAK,CAAC,KAAK;YACT,GAAG,CAAC,mBAAmB,CAAC,CAAC;YACzB,MAAM,CAAC,GAAG,IAAK,GAAuB,CAAC;gBACrC,KAAK,EAAE,CAAC,QAAQ,CAAC;gBACjB,IAAI,EAAE,SAAS;gBACf,YAAY,EAAE,SAAS;aACxB,CAAC,CAAC;YAEH,UAAU,GAAG,CAAC,CAAC;YAEf,MAAM,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QACD,QAAQ;YACN,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC;YAC7B,UAAU,GAAG,SAAS,CAAC;QACzB,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,OAAO;YACnB,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;YAErD,GAAG,CAAC,2BAA2B,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YACjD,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,OAAO;YACrB,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;YAErD,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE;gBAChC,GAAG,CAAC,yBAAyB,EAAE,IAAI,CAAC,CAAC;gBAErC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAEtC,IAAI,CAAC,OAAO;oBAAE,OAAO;gBAErB,OAAO,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;IACnB,MAAM,EAAE,OAAO;CAChB,CAAC,CAAC","sourcesContent":["import { SignalNoConnectionError } from \"@openlv/core/errors\";\nimport Gun, { type IGun, type IGunInstance } from \"gun\";\n\nimport { createSignalingLayer } from \"../index.js\";\nimport type { SignalingProtocol } from \"../protocol.js\";\nimport { log } from \"../utils/log.js\";\n\n/**\n * GunDB Signaling Layer\n * https://openlv.sh/api/signaling/gun\n */\nexport const gundb: SignalingProtocol = ({ topic, url }) => {\n const endpoint = url || \"wss://try.axe.eco/gun\";\n let connection: IGunInstance | undefined;\n\n return createSignalingLayer({\n type: \"gundb\",\n async setup() {\n log(\"GUNDB: Setting up\");\n const x = new (Gun as unknown as IGun)({\n peers: [endpoint],\n file: undefined,\n localStorage: undefined,\n });\n\n connection = x;\n\n await connection?.get(topic);\n },\n teardown() {\n connection?.get(topic).off();\n connection = undefined;\n },\n async publish(payload) {\n if (!connection) throw new SignalNoConnectionError();\n\n log(\"GUNDB: Publishing message\", topic, payload);\n connection.get(topic).put({ data: payload });\n },\n async subscribe(handler) {\n if (!connection) throw new SignalNoConnectionError();\n\n connection.get(topic).on((data) => {\n log(\"GUNDB: Received message\", data);\n\n const message = data.data?.toString();\n\n if (!message) return;\n\n handler(message);\n });\n },\n });\n};\n\nObject.assign(gundb, {\n __name: \"gundb\",\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { deriveSymmetricKey, generateKeyPair, hashPublicKey, parseEncryptionKey, } from "@openlv/core/encryption";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createSignalingLayer, SIGNAL_STATE } from "./index.js";
|
|
4
|
+
/**
|
|
5
|
+
* In-memory topic emulating a public relay: every publish is delivered to
|
|
6
|
+
* all subscribers (including the sender, like MQTT echo). A `drop` predicate
|
|
7
|
+
* simulates lossy delivery.
|
|
8
|
+
*/
|
|
9
|
+
const neverDrop = () => false;
|
|
10
|
+
const createTopic = () => {
|
|
11
|
+
const subscribers = [];
|
|
12
|
+
let drop = neverDrop;
|
|
13
|
+
let published = 0;
|
|
14
|
+
return {
|
|
15
|
+
setDrop(fn) {
|
|
16
|
+
drop = fn;
|
|
17
|
+
},
|
|
18
|
+
inject(payload) {
|
|
19
|
+
for (const subscriber of subscribers)
|
|
20
|
+
subscriber(payload);
|
|
21
|
+
},
|
|
22
|
+
channel() {
|
|
23
|
+
return {
|
|
24
|
+
type: "memory",
|
|
25
|
+
setup: () => { },
|
|
26
|
+
teardown: () => { },
|
|
27
|
+
publish: (payload) => {
|
|
28
|
+
const index = published++;
|
|
29
|
+
if (drop(payload, index))
|
|
30
|
+
return;
|
|
31
|
+
// Deliver asynchronously, as a real relay would.
|
|
32
|
+
queueMicrotask(() => {
|
|
33
|
+
for (const subscriber of subscribers)
|
|
34
|
+
subscriber(payload);
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
subscribe: (handler) => {
|
|
38
|
+
subscribers.push(handler);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const CLIENT_CAPABILITIES = {
|
|
45
|
+
transports: ["wrtc"],
|
|
46
|
+
info: { identity: "com.example.wallet", name: "Example Wallet" },
|
|
47
|
+
};
|
|
48
|
+
const createPeer = async (channel, { isHost, h, k }) => {
|
|
49
|
+
const { encryptionKey, decryptionKey } = await generateKeyPair();
|
|
50
|
+
let relyingKey;
|
|
51
|
+
let peerCapabilities;
|
|
52
|
+
const layer = await createSignalingLayer(channel)({
|
|
53
|
+
isHost,
|
|
54
|
+
h,
|
|
55
|
+
k: await deriveSymmetricKey(k),
|
|
56
|
+
publicKey: encryptionKey,
|
|
57
|
+
canEncrypt: () => relyingKey !== undefined,
|
|
58
|
+
encrypt: (message) => {
|
|
59
|
+
if (!relyingKey)
|
|
60
|
+
throw new Error("no peer key");
|
|
61
|
+
return relyingKey.encrypt(message);
|
|
62
|
+
},
|
|
63
|
+
decrypt: (message) => decryptionKey.decrypt(message),
|
|
64
|
+
rpDiscovered: async (rpKey) => {
|
|
65
|
+
relyingKey = await parseEncryptionKey(rpKey);
|
|
66
|
+
},
|
|
67
|
+
capabilities: CLIENT_CAPABILITIES,
|
|
68
|
+
peerCapabilities: (capabilities) => {
|
|
69
|
+
peerCapabilities = capabilities;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return { layer, encryptionKey, getPeerCapabilities: () => peerCapabilities };
|
|
73
|
+
};
|
|
74
|
+
const K = "00112233445566778899aabbccddeeff";
|
|
75
|
+
const setupPair = async (topic) => {
|
|
76
|
+
// Mirror createSession: the host's h is the hash of its own key, the
|
|
77
|
+
// client receives that h out-of-band (via the URI).
|
|
78
|
+
const hostKeys = await generateKeyPair();
|
|
79
|
+
const h = await hashPublicKey(hostKeys.encryptionKey);
|
|
80
|
+
const HOST_CAPABILITIES = {
|
|
81
|
+
transports: ["wrtc", "ws"],
|
|
82
|
+
info: { identity: "com.example.dapp", name: "Example dApp" },
|
|
83
|
+
};
|
|
84
|
+
let hostRelying;
|
|
85
|
+
let hostPeerCapabilities;
|
|
86
|
+
const host = await createSignalingLayer(topic.channel())({
|
|
87
|
+
isHost: true,
|
|
88
|
+
h,
|
|
89
|
+
k: await deriveSymmetricKey(K),
|
|
90
|
+
publicKey: hostKeys.encryptionKey,
|
|
91
|
+
canEncrypt: () => hostRelying !== undefined,
|
|
92
|
+
encrypt: (message) => {
|
|
93
|
+
if (!hostRelying)
|
|
94
|
+
throw new Error("no peer key");
|
|
95
|
+
return hostRelying.encrypt(message);
|
|
96
|
+
},
|
|
97
|
+
decrypt: (message) => hostKeys.decryptionKey.decrypt(message),
|
|
98
|
+
rpDiscovered: async (rpKey) => {
|
|
99
|
+
hostRelying = await parseEncryptionKey(rpKey);
|
|
100
|
+
},
|
|
101
|
+
capabilities: HOST_CAPABILITIES,
|
|
102
|
+
peerCapabilities: (capabilities) => {
|
|
103
|
+
hostPeerCapabilities = capabilities;
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const { layer: client, getPeerCapabilities: getClientPeerCapabilities } = await createPeer(topic.channel(), {
|
|
107
|
+
isHost: false,
|
|
108
|
+
h,
|
|
109
|
+
k: K,
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
host,
|
|
113
|
+
client,
|
|
114
|
+
hostCapabilities: HOST_CAPABILITIES,
|
|
115
|
+
getHostPeerCapabilities: () => hostPeerCapabilities,
|
|
116
|
+
getClientPeerCapabilities,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const waitForState = (layer, target) => new Promise((resolve, reject) => {
|
|
120
|
+
if (layer.getState().state === target)
|
|
121
|
+
return resolve();
|
|
122
|
+
layer.on("state_change", (state) => {
|
|
123
|
+
if (state === target)
|
|
124
|
+
resolve();
|
|
125
|
+
if (state === SIGNAL_STATE.ERROR && target !== SIGNAL_STATE.ERROR) {
|
|
126
|
+
reject(new Error("signaling errored"));
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe("signaling handshake (in-memory relay)", () => {
|
|
131
|
+
let host;
|
|
132
|
+
let client;
|
|
133
|
+
afterEach(async () => {
|
|
134
|
+
await host?.teardown();
|
|
135
|
+
await client?.teardown();
|
|
136
|
+
});
|
|
137
|
+
it("completes and exchanges data on a reliable relay", async () => {
|
|
138
|
+
const topic = createTopic();
|
|
139
|
+
const pair = await setupPair(topic);
|
|
140
|
+
({ host, client } = pair);
|
|
141
|
+
await host.setup();
|
|
142
|
+
const encrypted = Promise.all([
|
|
143
|
+
waitForState(host, SIGNAL_STATE.ENCRYPTED),
|
|
144
|
+
waitForState(client, SIGNAL_STATE.ENCRYPTED),
|
|
145
|
+
]);
|
|
146
|
+
await client.setup();
|
|
147
|
+
await encrypted;
|
|
148
|
+
const received = new Promise(resolve => host.on("message", resolve));
|
|
149
|
+
await client.send({ hello: "world" });
|
|
150
|
+
expect(await received).toEqual({ hello: "world" });
|
|
151
|
+
});
|
|
152
|
+
it("exchanges capabilities in both directions during the handshake", async () => {
|
|
153
|
+
const topic = createTopic();
|
|
154
|
+
const pair = await setupPair(topic);
|
|
155
|
+
({ host, client } = pair);
|
|
156
|
+
await host.setup();
|
|
157
|
+
const encrypted = Promise.all([
|
|
158
|
+
waitForState(host, SIGNAL_STATE.ENCRYPTED),
|
|
159
|
+
waitForState(client, SIGNAL_STATE.ENCRYPTED),
|
|
160
|
+
]);
|
|
161
|
+
await client.setup();
|
|
162
|
+
await encrypted;
|
|
163
|
+
expect(pair.getClientPeerCapabilities()).toEqual(pair.hostCapabilities);
|
|
164
|
+
expect(pair.getHostPeerCapabilities()).toEqual(CLIENT_CAPABILITIES);
|
|
165
|
+
});
|
|
166
|
+
it("ignores garbage frames on the public topic", async () => {
|
|
167
|
+
const topic = createTopic();
|
|
168
|
+
({ host, client } = await setupPair(topic));
|
|
169
|
+
await host.setup();
|
|
170
|
+
// Garbage before and during the handshake: random text, valid-looking
|
|
171
|
+
// frames with undecryptable bodies, and truncated frames.
|
|
172
|
+
topic.inject("not-a-frame");
|
|
173
|
+
topic.inject("hh");
|
|
174
|
+
topic.inject("hcQUJDRA==");
|
|
175
|
+
topic.inject("xh!!!not-base64!!!");
|
|
176
|
+
const encrypted = Promise.all([
|
|
177
|
+
waitForState(host, SIGNAL_STATE.ENCRYPTED),
|
|
178
|
+
waitForState(client, SIGNAL_STATE.ENCRYPTED),
|
|
179
|
+
]);
|
|
180
|
+
await client.setup();
|
|
181
|
+
topic.inject("hcgarbage-mid-handshake");
|
|
182
|
+
await encrypted;
|
|
183
|
+
});
|
|
184
|
+
describe("lossy relay", () => {
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
vi.useFakeTimers();
|
|
187
|
+
});
|
|
188
|
+
afterEach(() => {
|
|
189
|
+
vi.useRealTimers();
|
|
190
|
+
});
|
|
191
|
+
it("recovers when handshake frames are dropped", async () => {
|
|
192
|
+
const topic = createTopic();
|
|
193
|
+
// Drop the first delivery of every frame; re-sends get through.
|
|
194
|
+
const seen = new Set();
|
|
195
|
+
topic.setDrop((payload) => {
|
|
196
|
+
if (seen.has(payload.slice(0, 2) + payload.length))
|
|
197
|
+
return false;
|
|
198
|
+
seen.add(payload.slice(0, 2) + payload.length);
|
|
199
|
+
return true;
|
|
200
|
+
});
|
|
201
|
+
({ host, client } = await setupPair(topic));
|
|
202
|
+
await host.setup();
|
|
203
|
+
await client.setup();
|
|
204
|
+
const encrypted = Promise.all([
|
|
205
|
+
waitForState(host, SIGNAL_STATE.ENCRYPTED),
|
|
206
|
+
waitForState(client, SIGNAL_STATE.ENCRYPTED),
|
|
207
|
+
]);
|
|
208
|
+
// Let several resend intervals elapse.
|
|
209
|
+
for (let index = 0; index < 10; index += 1) {
|
|
210
|
+
await vi.advanceTimersByTimeAsync(2100);
|
|
211
|
+
}
|
|
212
|
+
await encrypted;
|
|
213
|
+
});
|
|
214
|
+
it("errors out when the peer never appears", async () => {
|
|
215
|
+
const topic = createTopic();
|
|
216
|
+
topic.setDrop(() => true);
|
|
217
|
+
({ host, client } = await setupPair(topic));
|
|
218
|
+
await client.setup();
|
|
219
|
+
const errored = waitForState(client, SIGNAL_STATE.ERROR);
|
|
220
|
+
await vi.advanceTimersByTimeAsync(31000);
|
|
221
|
+
await errored;
|
|
222
|
+
expect(client.getState().state).toBe(SIGNAL_STATE.ERROR);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
//# sourceMappingURL=handshake.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake.test.js","sourceRoot":"","sources":["../src/handshake.test.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n deriveSymmetricKey,\n type EncryptionKey,\n generateKeyPair,\n hashPublicKey,\n parseEncryptionKey,\n} from \"@openlv/core/encryption\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createSignalingLayer, SIGNAL_STATE, type SignalingLayer } from \"./index.js\";\nimport type { PeerCapabilities } from \"./messages.js\";\nimport type { SignalingChannel } from \"./protocol.js\";\n\n/**\n * In-memory topic emulating a public relay: every publish is delivered to\n * all subscribers (including the sender, like MQTT echo). A `drop` predicate\n * simulates lossy delivery.\n */\nconst neverDrop = () => false;\n\nconst createTopic = () => {\n const subscribers: ((payload: string) => void)[] = [];\n let drop: (payload: string, index: number) => boolean = neverDrop;\n let published = 0;\n\n return {\n setDrop(fn: (payload: string, index: number) => boolean) {\n drop = fn;\n },\n inject(payload: string) {\n for (const subscriber of subscribers) subscriber(payload);\n },\n channel(): SignalingChannel {\n return {\n type: \"memory\",\n setup: () => {},\n teardown: () => {},\n publish: (payload: string) => {\n const index = published++;\n\n if (drop(payload, index)) return;\n\n // Deliver asynchronously, as a real relay would.\n queueMicrotask(() => {\n for (const subscriber of subscribers) subscriber(payload);\n });\n },\n subscribe: (handler) => {\n subscribers.push(handler);\n },\n };\n },\n };\n};\n\nconst CLIENT_CAPABILITIES: PeerCapabilities = {\n transports: [\"wrtc\"],\n info: { identity: \"com.example.wallet\", name: \"Example Wallet\" },\n};\n\nconst createPeer = async (\n channel: SignalingChannel,\n { isHost, h, k }: { isHost: boolean; h: string; k: string; },\n) => {\n const { encryptionKey, decryptionKey } = await generateKeyPair();\n let relyingKey: EncryptionKey | undefined;\n let peerCapabilities: PeerCapabilities | undefined;\n\n const layer = await createSignalingLayer(channel)({\n isHost,\n h,\n k: await deriveSymmetricKey(k),\n publicKey: encryptionKey,\n canEncrypt: () => relyingKey !== undefined,\n encrypt: (message: string) => {\n if (!relyingKey) throw new Error(\"no peer key\");\n\n return relyingKey.encrypt(message);\n },\n decrypt: (message: string) => decryptionKey.decrypt(message),\n rpDiscovered: async (rpKey: string) => {\n relyingKey = await parseEncryptionKey(rpKey);\n },\n capabilities: CLIENT_CAPABILITIES,\n peerCapabilities: (capabilities) => {\n peerCapabilities = capabilities;\n },\n });\n\n return { layer, encryptionKey, getPeerCapabilities: () => peerCapabilities };\n};\n\nconst K = \"00112233445566778899aabbccddeeff\";\n\nconst setupPair = async (topic: ReturnType<typeof createTopic>) => {\n // Mirror createSession: the host's h is the hash of its own key, the\n // client receives that h out-of-band (via the URI).\n const hostKeys = await generateKeyPair();\n const h = await hashPublicKey(hostKeys.encryptionKey);\n\n const HOST_CAPABILITIES: PeerCapabilities = {\n transports: [\"wrtc\", \"ws\"],\n info: { identity: \"com.example.dapp\", name: \"Example dApp\" },\n };\n\n let hostRelying: EncryptionKey | undefined;\n let hostPeerCapabilities: PeerCapabilities | undefined;\n const host = await createSignalingLayer(topic.channel())({\n isHost: true,\n h,\n k: await deriveSymmetricKey(K),\n publicKey: hostKeys.encryptionKey,\n canEncrypt: () => hostRelying !== undefined,\n encrypt: (message: string) => {\n if (!hostRelying) throw new Error(\"no peer key\");\n\n return hostRelying.encrypt(message);\n },\n decrypt: (message: string) => hostKeys.decryptionKey.decrypt(message),\n rpDiscovered: async (rpKey: string) => {\n hostRelying = await parseEncryptionKey(rpKey);\n },\n capabilities: HOST_CAPABILITIES,\n peerCapabilities: (capabilities) => {\n hostPeerCapabilities = capabilities;\n },\n });\n\n const { layer: client, getPeerCapabilities: getClientPeerCapabilities }\n = await createPeer(topic.channel(), {\n isHost: false,\n h,\n k: K,\n });\n\n return {\n host,\n client,\n hostCapabilities: HOST_CAPABILITIES,\n getHostPeerCapabilities: () => hostPeerCapabilities,\n getClientPeerCapabilities,\n };\n};\n\nconst waitForState = (layer: SignalingLayer, target: string) =>\n new Promise<void>((resolve, reject) => {\n if (layer.getState().state === target) return resolve();\n\n layer.on(\"state_change\", (state) => {\n if (state === target) resolve();\n\n if (state === SIGNAL_STATE.ERROR && target !== SIGNAL_STATE.ERROR) {\n reject(new Error(\"signaling errored\"));\n }\n });\n });\n\ndescribe(\"signaling handshake (in-memory relay)\", () => {\n let host: SignalingLayer;\n let client: SignalingLayer;\n\n afterEach(async () => {\n await host?.teardown();\n await client?.teardown();\n });\n\n it(\"completes and exchanges data on a reliable relay\", async () => {\n const topic = createTopic();\n const pair = await setupPair(topic);\n\n ({ host, client } = pair);\n\n await host.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n await encrypted;\n\n const received = new Promise<object>(resolve => host.on(\"message\", resolve));\n\n await client.send({ hello: \"world\" });\n expect(await received).toEqual({ hello: \"world\" });\n });\n\n it(\"exchanges capabilities in both directions during the handshake\", async () => {\n const topic = createTopic();\n const pair = await setupPair(topic);\n\n ({ host, client } = pair);\n\n await host.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n await encrypted;\n\n expect(pair.getClientPeerCapabilities()).toEqual(pair.hostCapabilities);\n expect(pair.getHostPeerCapabilities()).toEqual(CLIENT_CAPABILITIES);\n });\n\n it(\"ignores garbage frames on the public topic\", async () => {\n const topic = createTopic();\n\n ({ host, client } = await setupPair(topic));\n\n await host.setup();\n\n // Garbage before and during the handshake: random text, valid-looking\n // frames with undecryptable bodies, and truncated frames.\n topic.inject(\"not-a-frame\");\n topic.inject(\"hh\");\n topic.inject(\"hcQUJDRA==\");\n topic.inject(\"xh!!!not-base64!!!\");\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n topic.inject(\"hcgarbage-mid-handshake\");\n await encrypted;\n });\n\n describe(\"lossy relay\", () => {\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n it(\"recovers when handshake frames are dropped\", async () => {\n const topic = createTopic();\n\n // Drop the first delivery of every frame; re-sends get through.\n const seen = new Set<string>();\n\n topic.setDrop((payload) => {\n if (seen.has(payload.slice(0, 2) + payload.length)) return false;\n\n seen.add(payload.slice(0, 2) + payload.length);\n\n return true;\n });\n\n ({ host, client } = await setupPair(topic));\n\n await host.setup();\n await client.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n // Let several resend intervals elapse.\n for (let index = 0; index < 10; index += 1) {\n await vi.advanceTimersByTimeAsync(2100);\n }\n\n await encrypted;\n });\n\n it(\"errors out when the peer never appears\", async () => {\n const topic = createTopic();\n\n topic.setDrop(() => true);\n\n ({ host, client } = await setupPair(topic));\n\n await client.setup();\n\n const errored = waitForState(client, SIGNAL_STATE.ERROR);\n\n await vi.advanceTimersByTimeAsync(31_000);\n await errored;\n expect(client.getState().state).toBe(SIGNAL_STATE.ERROR);\n });\n });\n});\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { EncryptionKey, SymmetricKey } from "@openlv/core/encryption";
|
|
2
2
|
import { EventEmitter } from "eventemitter3";
|
|
3
3
|
import type { MaybePromise } from "viem";
|
|
4
|
+
import { type PeerCapabilities } from "./messages.js";
|
|
4
5
|
import type { SignalingChannel } from "./protocol.js";
|
|
6
|
+
export * from "./messages.js";
|
|
5
7
|
export * from "./protocol.js";
|
|
6
8
|
export declare const SIGNAL_STATE: {
|
|
7
9
|
readonly STANDBY: "standby";
|
|
@@ -22,6 +24,8 @@ export type SignalingProperties = {
|
|
|
22
24
|
h: string;
|
|
23
25
|
k?: SymmetricKey;
|
|
24
26
|
rpDiscovered: (rpKey: string) => MaybePromise<void>;
|
|
27
|
+
capabilities: PeerCapabilities;
|
|
28
|
+
peerCapabilities: (capabilities: PeerCapabilities) => MaybePromise<void>;
|
|
25
29
|
decrypt: (message: string) => MaybePromise<string>;
|
|
26
30
|
encrypt: (message: string) => MaybePromise<string>;
|
|
27
31
|
publicKey: EncryptionKey;
|