@leofcoin/chain 1.8.8 → 1.8.11
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/exports/browser/chain.js +18 -12
- package/exports/browser/{flags-wmeqg14g.js → constants-ByuwyBVy.js} +772 -82
- package/exports/browser/node-browser.js +84 -4
- package/exports/chain.js +14 -7
- package/exports/constants-eo0U5-D_.js +12 -0
- package/exports/constants.d.ts +2 -0
- package/exports/node.js +2 -9
- package/package.json +4 -4
- package/exports/browser/browser-CfYI-6aD-DHRKebpJ.js +0 -132
- package/exports/browser/browser-Qcpp3EKK-DOtgsScX.js +0 -38
- package/exports/browser/client-CWkdUcxK-BEmg-tGc.js +0 -989
- package/exports/browser/identity-nIyW_Xm8-BU8xakCv.js +0 -17199
- package/exports/browser/index-ChRjMyiM-EjbBu23l.js +0 -36
- package/exports/browser/index-DTbjK0sK-BK_5FT46.js +0 -7580
- package/exports/browser/messages-C507MMRx-ypXoH7gN.js +0 -207
- package/exports/browser/networks-F1y7bMrB.js +0 -25
- package/exports/browser/node-browser-A1KVCavN.js +0 -9132
- package/exports/browser/password-oDixGC8h.js +0 -3
- package/exports/browser/qr-scanner-worker.min-Dy0qkKA4-Dy0qkKA4.js +0 -100
- package/exports/flags-DKVwPDLE.js +0 -3
- package/exports/flags.d.ts +0 -2
|
@@ -1,989 +0,0 @@
|
|
|
1
|
-
import { c as createDebugger, i as inflate_1, d as deflate_1, L as LittlePubSub } from './node-browser-A1KVCavN.js';
|
|
2
|
-
import './identity-nIyW_Xm8-BU8xakCv.js';
|
|
3
|
-
import './flags-wmeqg14g.js';
|
|
4
|
-
import './networks-F1y7bMrB.js';
|
|
5
|
-
|
|
6
|
-
class Api {
|
|
7
|
-
_pubsub;
|
|
8
|
-
constructor(_pubsub) {
|
|
9
|
-
this._pubsub = _pubsub;
|
|
10
|
-
}
|
|
11
|
-
subscribe(topic, cb) {
|
|
12
|
-
this._pubsub.subscribe(topic, cb);
|
|
13
|
-
}
|
|
14
|
-
unsubscribe(topic, cb) {
|
|
15
|
-
this._pubsub.unsubscribe(topic, cb);
|
|
16
|
-
}
|
|
17
|
-
publish(topic, value) {
|
|
18
|
-
this._pubsub.publish(topic, value);
|
|
19
|
-
}
|
|
20
|
-
subscribers() {
|
|
21
|
-
this._pubsub.subscribers;
|
|
22
|
-
}
|
|
23
|
-
connectionState(state) {
|
|
24
|
-
switch (state) {
|
|
25
|
-
case 0:
|
|
26
|
-
return 'connecting';
|
|
27
|
-
case 1:
|
|
28
|
-
return 'open';
|
|
29
|
-
case 2:
|
|
30
|
-
return 'closing';
|
|
31
|
-
case 3:
|
|
32
|
-
return 'closed';
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* @param {string} type
|
|
37
|
-
* @param {string} name
|
|
38
|
-
* @param {object} params
|
|
39
|
-
*/
|
|
40
|
-
request(client, request) {
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
const state = this.connectionState(client.readyState);
|
|
43
|
-
if (state !== 'open')
|
|
44
|
-
return reject(`coudn't send request to ${client.id}, no open connection found.`);
|
|
45
|
-
request.id = Math.random().toString(36).slice(-12);
|
|
46
|
-
const handler = (result) => {
|
|
47
|
-
if (result && result.error)
|
|
48
|
-
return reject(result.error);
|
|
49
|
-
resolve({ result, id: request.id, handler });
|
|
50
|
-
this.unsubscribe(request.id, handler);
|
|
51
|
-
};
|
|
52
|
-
this.subscribe(request.id, handler);
|
|
53
|
-
this.send(client, request);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
async send(client, request) {
|
|
57
|
-
return client.send(JSON.stringify(request));
|
|
58
|
-
}
|
|
59
|
-
pubsub(client) {
|
|
60
|
-
return {
|
|
61
|
-
publish: (topic = 'pubsub', value) => {
|
|
62
|
-
return this.send(client, { url: 'pubsub', params: { topic, value } });
|
|
63
|
-
},
|
|
64
|
-
subscribe: (topic = 'pubsub', cb) => {
|
|
65
|
-
this.subscribe(topic, cb);
|
|
66
|
-
return this.send(client, {
|
|
67
|
-
url: 'pubsub',
|
|
68
|
-
params: { topic, subscribe: true }
|
|
69
|
-
});
|
|
70
|
-
},
|
|
71
|
-
unsubscribe: (topic = 'pubsub', cb) => {
|
|
72
|
-
this.unsubscribe(topic, cb);
|
|
73
|
-
return this.send(client, {
|
|
74
|
-
url: 'pubsub',
|
|
75
|
-
params: { topic, unsubscribe: true }
|
|
76
|
-
});
|
|
77
|
-
},
|
|
78
|
-
subscribers: this._pubsub.subscribers
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
server(client) {
|
|
82
|
-
return {
|
|
83
|
-
uptime: async () => {
|
|
84
|
-
try {
|
|
85
|
-
const { result, id, handler } = await this.request(client, {
|
|
86
|
-
url: 'uptime'
|
|
87
|
-
});
|
|
88
|
-
this.unsubscribe(id, handler);
|
|
89
|
-
return result;
|
|
90
|
-
}
|
|
91
|
-
catch (e) {
|
|
92
|
-
throw e;
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
ping: async () => {
|
|
96
|
-
try {
|
|
97
|
-
const now = new Date().getTime();
|
|
98
|
-
const { result, id, handler } = await this.request(client, {
|
|
99
|
-
url: 'ping'
|
|
100
|
-
});
|
|
101
|
-
this.unsubscribe(id, handler);
|
|
102
|
-
return Number(result) - now;
|
|
103
|
-
}
|
|
104
|
-
catch (e) {
|
|
105
|
-
throw e;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
peernet(client) {
|
|
111
|
-
return {
|
|
112
|
-
join: async (params) => {
|
|
113
|
-
try {
|
|
114
|
-
params.join = true;
|
|
115
|
-
const requested = { url: 'peernet', params };
|
|
116
|
-
const { result, id, handler } = await this.request(client, requested);
|
|
117
|
-
this.unsubscribe(id, handler);
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
catch (e) {
|
|
121
|
-
throw e;
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
leave: async (params) => {
|
|
125
|
-
try {
|
|
126
|
-
params.join = false;
|
|
127
|
-
const requested = { url: 'peernet', params };
|
|
128
|
-
const { result, id, handler } = await this.request(client, requested);
|
|
129
|
-
this.unsubscribe(id, handler);
|
|
130
|
-
return result;
|
|
131
|
-
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
throw e;
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
peers: async () => {
|
|
137
|
-
try {
|
|
138
|
-
const requested = { url: 'peernet', params: { peers: true } };
|
|
139
|
-
const { result, id, handler } = await this.request(client, requested);
|
|
140
|
-
this.unsubscribe(id, handler);
|
|
141
|
-
return result;
|
|
142
|
-
}
|
|
143
|
-
catch (e) {
|
|
144
|
-
throw e;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
class ClientConnection {
|
|
152
|
-
client;
|
|
153
|
-
api;
|
|
154
|
-
#startTime;
|
|
155
|
-
constructor(client, api) {
|
|
156
|
-
this.#startTime = new Date().getTime();
|
|
157
|
-
this.client = client;
|
|
158
|
-
this.api = api;
|
|
159
|
-
}
|
|
160
|
-
request = async (req) => {
|
|
161
|
-
const { result, id, handler } = await this.api.request(this.client, req);
|
|
162
|
-
globalThis.pubsub.unsubscribe(id, handler);
|
|
163
|
-
return result;
|
|
164
|
-
};
|
|
165
|
-
send = (req) => this.api.send(this.client, req);
|
|
166
|
-
get subscribe() {
|
|
167
|
-
return this.api.subscribe;
|
|
168
|
-
}
|
|
169
|
-
get unsubscribe() {
|
|
170
|
-
return this.api.unsubscribe;
|
|
171
|
-
}
|
|
172
|
-
get subscribers() {
|
|
173
|
-
return this.api.subscribers;
|
|
174
|
-
}
|
|
175
|
-
get publish() {
|
|
176
|
-
return this.api.publish;
|
|
177
|
-
}
|
|
178
|
-
get pubsub() {
|
|
179
|
-
return this.api.pubsub(this.client);
|
|
180
|
-
}
|
|
181
|
-
uptime = () => {
|
|
182
|
-
const now = new Date().getTime();
|
|
183
|
-
return (now - this.#startTime);
|
|
184
|
-
};
|
|
185
|
-
get peernet() {
|
|
186
|
-
return this.api.peernet(this.client);
|
|
187
|
-
}
|
|
188
|
-
get server() {
|
|
189
|
-
return this.api.server(this.client);
|
|
190
|
-
}
|
|
191
|
-
connectionState = () => this.api.connectionState(this.client.readyState);
|
|
192
|
-
close = exit => {
|
|
193
|
-
// client.onclose = message => {
|
|
194
|
-
// if (exit) process.exit()
|
|
195
|
-
// }
|
|
196
|
-
this.client.close();
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (!globalThis.PubSub)
|
|
201
|
-
globalThis.PubSub = LittlePubSub;
|
|
202
|
-
if (!globalThis.pubsub)
|
|
203
|
-
globalThis.pubsub = new LittlePubSub(false);
|
|
204
|
-
class SocketRequestClient {
|
|
205
|
-
api;
|
|
206
|
-
clientConnection;
|
|
207
|
-
#tries = 0;
|
|
208
|
-
#retry = false;
|
|
209
|
-
#timeout = 10_000;
|
|
210
|
-
#times = 10;
|
|
211
|
-
#options;
|
|
212
|
-
#protocol;
|
|
213
|
-
#url;
|
|
214
|
-
#experimentalWebsocket = false;
|
|
215
|
-
constructor(url, protocol, options) {
|
|
216
|
-
let { retry, timeout, times, experimentalWebsocket } = options || {};
|
|
217
|
-
if (retry !== undefined)
|
|
218
|
-
this.#retry = retry;
|
|
219
|
-
if (timeout !== undefined)
|
|
220
|
-
this.#timeout = timeout;
|
|
221
|
-
if (times !== undefined)
|
|
222
|
-
this.#times = times;
|
|
223
|
-
if (experimentalWebsocket !== undefined)
|
|
224
|
-
this.#experimentalWebsocket;
|
|
225
|
-
this.#url = url;
|
|
226
|
-
this.#protocol = protocol;
|
|
227
|
-
this.#options = options;
|
|
228
|
-
this.api = new Api(globalThis.pubsub);
|
|
229
|
-
}
|
|
230
|
-
init() {
|
|
231
|
-
return new Promise(async (resolve, reject) => {
|
|
232
|
-
const init = async () => {
|
|
233
|
-
// @ts-ignore
|
|
234
|
-
if (!globalThis.WebSocket && !this.#experimentalWebsocket)
|
|
235
|
-
globalThis.WebSocket = (await import('./browser-CfYI-6aD-DHRKebpJ.js').then(function (n) { return n.b; })).default.w3cwebsocket;
|
|
236
|
-
const client = new WebSocket(this.#url, this.#protocol);
|
|
237
|
-
if (this.#experimentalWebsocket) {
|
|
238
|
-
client.addEventListener('error', this.onerror);
|
|
239
|
-
client.addEventListener('message', this.onmessage);
|
|
240
|
-
client.addEventListener('open', () => {
|
|
241
|
-
this.#tries = 0;
|
|
242
|
-
resolve(new ClientConnection(client, this.api));
|
|
243
|
-
});
|
|
244
|
-
client.addEventListener('close', (client.onclose = (message) => {
|
|
245
|
-
this.#tries++;
|
|
246
|
-
if (!this.#retry)
|
|
247
|
-
return reject(this.#options);
|
|
248
|
-
if (this.#tries > this.#times) {
|
|
249
|
-
console.log(`${this.#options.protocol} Client Closed`);
|
|
250
|
-
console.error(`could not connect to - ${this.#url}/`);
|
|
251
|
-
return resolve(new ClientConnection(client, this.api));
|
|
252
|
-
}
|
|
253
|
-
if (message.code === 1006) {
|
|
254
|
-
console.log(`Retrying in ${this.#timeout} ms`);
|
|
255
|
-
setTimeout(() => {
|
|
256
|
-
return init();
|
|
257
|
-
}, this.#timeout);
|
|
258
|
-
}
|
|
259
|
-
}));
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
client.onmessage = this.onmessage;
|
|
263
|
-
client.onerror = this.onerror;
|
|
264
|
-
client.onopen = () => {
|
|
265
|
-
this.#tries = 0;
|
|
266
|
-
resolve(new ClientConnection(client, this.api));
|
|
267
|
-
};
|
|
268
|
-
client.onclose = (message) => {
|
|
269
|
-
this.#tries++;
|
|
270
|
-
if (!this.#retry)
|
|
271
|
-
return reject(this.#options);
|
|
272
|
-
if (this.#tries > this.#times) {
|
|
273
|
-
console.log(`${this.#options.protocol} Client Closed`);
|
|
274
|
-
console.error(`could not connect to - ${this.#url}/`);
|
|
275
|
-
return resolve(new ClientConnection(client, this.api));
|
|
276
|
-
}
|
|
277
|
-
if (message.code === 1006) {
|
|
278
|
-
console.log(`Retrying in ${this.#timeout} ms`);
|
|
279
|
-
setTimeout(() => {
|
|
280
|
-
return init();
|
|
281
|
-
}, this.#timeout);
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
return init();
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
onerror = (error) => {
|
|
290
|
-
if (globalThis.pubsub.subscribers['error']) {
|
|
291
|
-
globalThis.pubsub.publish('error', error);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
console.error(error);
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
onmessage(message) {
|
|
298
|
-
if (!message.data) {
|
|
299
|
-
console.warn(`message ignored because it contained no data`);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
const { value, url, status, id } = JSON.parse(message.data.toString());
|
|
303
|
-
const publisher = id ? id : url;
|
|
304
|
-
if (status === 200) {
|
|
305
|
-
globalThis.pubsub.publish(publisher, value);
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
globalThis.pubsub.publish(publisher, { error: value });
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const MAX_MESSAGE_SIZE = 16000;
|
|
314
|
-
const defaultOptions = {
|
|
315
|
-
networkVersion: 'peach',
|
|
316
|
-
version: 'v1',
|
|
317
|
-
stars: ['wss://star.leofcoin.org'],
|
|
318
|
-
connectEvent: 'peer:connected'
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
// Simple CRC32 implementation
|
|
322
|
-
const crc32$1 = (data) => {
|
|
323
|
-
let crc = 0xffffffff;
|
|
324
|
-
for (let i = 0; i < data.length; i++) {
|
|
325
|
-
crc ^= data[i];
|
|
326
|
-
for (let j = 0; j < 8; j++) {
|
|
327
|
-
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
331
|
-
};
|
|
332
|
-
const iceServers = [
|
|
333
|
-
{
|
|
334
|
-
urls: 'stun:stun.l.google.com:19302' // Google's public STUN server
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
urls: 'stun:openrelay.metered.ca:80'
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
urls: 'turn:openrelay.metered.ca:443',
|
|
341
|
-
username: 'openrelayproject',
|
|
342
|
-
credential: 'openrelayproject'
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
urls: 'turn:openrelay.metered.ca:443?transport=tcp',
|
|
346
|
-
username: 'openrelayproject',
|
|
347
|
-
credential: 'openrelayproject'
|
|
348
|
-
}
|
|
349
|
-
];
|
|
350
|
-
const SimplePeer = (await import('./index-DTbjK0sK-BK_5FT46.js').then(function (n) { return n.i; })).default;
|
|
351
|
-
class Peer extends SimplePeer {
|
|
352
|
-
peerId;
|
|
353
|
-
channelName;
|
|
354
|
-
version;
|
|
355
|
-
compressionThreshold = 0.98;
|
|
356
|
-
bw = { up: 0, down: 0 };
|
|
357
|
-
get connected() {
|
|
358
|
-
return super.connected;
|
|
359
|
-
}
|
|
360
|
-
constructor(options) {
|
|
361
|
-
const { from, to, initiator, trickle, config, version, wrtc, compressionThreshold } = options;
|
|
362
|
-
const channelName = initiator ? `${from}:${to}` : `${to}:${from}`;
|
|
363
|
-
super({
|
|
364
|
-
channelName,
|
|
365
|
-
initiator,
|
|
366
|
-
trickle: trickle ?? true,
|
|
367
|
-
config: { iceServers, ...config },
|
|
368
|
-
wrtc: wrtc ?? globalThis.wrtc
|
|
369
|
-
});
|
|
370
|
-
this.version = String(version);
|
|
371
|
-
this.peerId = to;
|
|
372
|
-
this.channelName = channelName;
|
|
373
|
-
if (compressionThreshold !== undefined)
|
|
374
|
-
this.compressionThreshold = compressionThreshold;
|
|
375
|
-
}
|
|
376
|
-
async #chunkit(data, id) {
|
|
377
|
-
this.bw.up = data.length;
|
|
378
|
-
// attempt compression; use compressed only if beneficial
|
|
379
|
-
let sendData = data;
|
|
380
|
-
try {
|
|
381
|
-
const c = deflate_1(data);
|
|
382
|
-
if (c?.length && c.length < data.length * this.compressionThreshold)
|
|
383
|
-
sendData = c;
|
|
384
|
-
}
|
|
385
|
-
catch (e) {
|
|
386
|
-
// ignore
|
|
387
|
-
}
|
|
388
|
-
const size = sendData.length;
|
|
389
|
-
const encodeFrame = (idStr, totalSize, index, count, payload, flags) => {
|
|
390
|
-
const te = new TextEncoder();
|
|
391
|
-
const idBytes = te.encode(idStr);
|
|
392
|
-
const crc = crc32$1(payload);
|
|
393
|
-
const headerLen = 1 + 1 + 4 + 4 + 4 + 4 + 2 + idBytes.length;
|
|
394
|
-
const buffer = new ArrayBuffer(headerLen + payload.length);
|
|
395
|
-
const view = new DataView(buffer);
|
|
396
|
-
const out = new Uint8Array(buffer);
|
|
397
|
-
let offset = 0;
|
|
398
|
-
view.setUint8(offset, 1); // version
|
|
399
|
-
offset += 1;
|
|
400
|
-
view.setUint8(offset, flags); // flags: bit0 chunked, bit1 compressed
|
|
401
|
-
offset += 1;
|
|
402
|
-
view.setUint32(offset, totalSize, true);
|
|
403
|
-
offset += 4;
|
|
404
|
-
view.setUint32(offset, index, true);
|
|
405
|
-
offset += 4;
|
|
406
|
-
view.setUint32(offset, count, true);
|
|
407
|
-
offset += 4;
|
|
408
|
-
view.setUint32(offset, crc, true); // CRC32
|
|
409
|
-
offset += 4;
|
|
410
|
-
view.setUint16(offset, idBytes.length, true);
|
|
411
|
-
offset += 2;
|
|
412
|
-
out.set(idBytes, offset);
|
|
413
|
-
offset += idBytes.length;
|
|
414
|
-
out.set(payload, offset);
|
|
415
|
-
return out;
|
|
416
|
-
};
|
|
417
|
-
// no needles chunking, keep it simple, if data is smaller then max size just send it
|
|
418
|
-
if (size <= MAX_MESSAGE_SIZE) {
|
|
419
|
-
const flags = ((size > MAX_MESSAGE_SIZE ? 1 : 0) << 0) |
|
|
420
|
-
((sendData !== data ? 1 : 0) << 1);
|
|
421
|
-
super.send(encodeFrame(id, size, 0, 1, sendData, flags));
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
function* chunks(data) {
|
|
425
|
-
while (data.length !== 0) {
|
|
426
|
-
const amountToSlice = data.length >= MAX_MESSAGE_SIZE ? MAX_MESSAGE_SIZE : data.length;
|
|
427
|
-
const subArray = data.subarray(0, amountToSlice);
|
|
428
|
-
data = data.subarray(amountToSlice, data.length);
|
|
429
|
-
yield subArray;
|
|
430
|
-
// super.send(JSON.stringify({ chunk: subArray, id, size }))
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
// while (data.length !== 0) {
|
|
434
|
-
// const amountToSlice =
|
|
435
|
-
// data.length >= MAX_MESSAGE_SIZE ? MAX_MESSAGE_SIZE : data.length
|
|
436
|
-
// const subArray = data.subarray(0, amountToSlice)
|
|
437
|
-
// data = data.subarray(amountToSlice, data.length)
|
|
438
|
-
// super.send(JSON.stringify({ chunk: subArray, id, size }))
|
|
439
|
-
// }
|
|
440
|
-
// backpressure-aware send loop with indexed chunks
|
|
441
|
-
const count = Math.ceil(size / MAX_MESSAGE_SIZE);
|
|
442
|
-
let index = 0;
|
|
443
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
444
|
-
const threshold = 4 * 1024 * 1024; // 4MB bufferedAmount threshold
|
|
445
|
-
const flags = (1 << 0) | ((sendData !== data ? 1 : 0) << 1);
|
|
446
|
-
for (const chunk of chunks(sendData)) {
|
|
447
|
-
// wait while channel is congested
|
|
448
|
-
// eslint-disable-next-line no-await-in-loop
|
|
449
|
-
while (
|
|
450
|
-
// @ts-ignore underlying channel is not part of public types
|
|
451
|
-
this._channel?.bufferedAmount > threshold) {
|
|
452
|
-
// if connection closed, abort
|
|
453
|
-
// eslint-disable-next-line no-await-in-loop
|
|
454
|
-
if (!this.connected)
|
|
455
|
-
return;
|
|
456
|
-
// eslint-disable-next-line no-await-in-loop
|
|
457
|
-
await sleep(10);
|
|
458
|
-
}
|
|
459
|
-
super.send(encodeFrame(id, size, index, count, chunk, flags));
|
|
460
|
-
index += 1;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* send to peer
|
|
465
|
-
* @param data ArrayLike
|
|
466
|
-
* @param id custom id to listen to
|
|
467
|
-
*/
|
|
468
|
-
send(data, id = crypto.randomUUID()) {
|
|
469
|
-
// send chuncks till ndata support for SCTP is added
|
|
470
|
-
// wraps data
|
|
471
|
-
this.#chunkit(data, id);
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* send to peer & wait for response
|
|
475
|
-
* @param data ArrayLike
|
|
476
|
-
* @param id custom id to listen to
|
|
477
|
-
*/
|
|
478
|
-
request(data, id = crypto.randomUUID()) {
|
|
479
|
-
return new Promise((resolve, reject) => {
|
|
480
|
-
let timeout;
|
|
481
|
-
const onrequest = ({ data }) => {
|
|
482
|
-
clearTimeout(timeout);
|
|
483
|
-
resolve(data);
|
|
484
|
-
globalThis.pubsub.unsubscribe(id, onrequest);
|
|
485
|
-
};
|
|
486
|
-
timeout = setTimeout(() => {
|
|
487
|
-
globalThis.pubsub.unsubscribe(id, onrequest);
|
|
488
|
-
reject(`request for ${id} timed out`);
|
|
489
|
-
}, 30_000);
|
|
490
|
-
globalThis.pubsub.subscribe(id, onrequest);
|
|
491
|
-
this.send(data, id);
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
toJSON() {
|
|
495
|
-
return {
|
|
496
|
-
peerId: this.peerId,
|
|
497
|
-
channelName: this.channelName,
|
|
498
|
-
version: this.version,
|
|
499
|
-
bw: this.bw
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Simple CRC32 implementation
|
|
505
|
-
const crc32 = (data) => {
|
|
506
|
-
let crc = 0xffffffff;
|
|
507
|
-
for (let i = 0; i < data.length; i++) {
|
|
508
|
-
crc ^= data[i];
|
|
509
|
-
for (let j = 0; j < 8; j++) {
|
|
510
|
-
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
514
|
-
};
|
|
515
|
-
const debug = createDebugger('@netpeer/swarm/client');
|
|
516
|
-
class Client {
|
|
517
|
-
#peerId;
|
|
518
|
-
#connections = {};
|
|
519
|
-
#stars = {};
|
|
520
|
-
#starListeners = {};
|
|
521
|
-
#reinitLock = null;
|
|
522
|
-
#connectEvent = 'peer:connected';
|
|
523
|
-
#retryOptions = { retries: 5, factor: 2, minTimeout: 1000, maxTimeout: 30000 };
|
|
524
|
-
id;
|
|
525
|
-
networkVersion;
|
|
526
|
-
starsConfig;
|
|
527
|
-
socketClient;
|
|
528
|
-
messageSize = 262144;
|
|
529
|
-
version;
|
|
530
|
-
#messagesToHandle = {};
|
|
531
|
-
get peerId() {
|
|
532
|
-
return this.#peerId;
|
|
533
|
-
}
|
|
534
|
-
get connections() {
|
|
535
|
-
return { ...this.#connections };
|
|
536
|
-
}
|
|
537
|
-
get peers() {
|
|
538
|
-
return Object.entries(this.#connections);
|
|
539
|
-
}
|
|
540
|
-
getPeer(peerId) {
|
|
541
|
-
return this.#connections[peerId];
|
|
542
|
-
}
|
|
543
|
-
constructor(options) {
|
|
544
|
-
const { peerId, networkVersion, version, connectEvent, stars } = {
|
|
545
|
-
...defaultOptions,
|
|
546
|
-
...options
|
|
547
|
-
};
|
|
548
|
-
this.#peerId = peerId;
|
|
549
|
-
this.networkVersion = networkVersion;
|
|
550
|
-
this.version = version;
|
|
551
|
-
this.#connectEvent = connectEvent;
|
|
552
|
-
this.starsConfig = stars;
|
|
553
|
-
if (options?.retry)
|
|
554
|
-
this.#retryOptions = { ...this.#retryOptions, ...options.retry };
|
|
555
|
-
this._init();
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Safely reinitialize the client (used after system resume/sleep).
|
|
559
|
-
* It closes existing connections and reconnects to configured stars.
|
|
560
|
-
*/
|
|
561
|
-
async reinit() {
|
|
562
|
-
// avoid concurrent reinit runs
|
|
563
|
-
if (this.#reinitLock)
|
|
564
|
-
return this.#reinitLock;
|
|
565
|
-
this.#reinitLock = (async () => {
|
|
566
|
-
debug('reinit: start');
|
|
567
|
-
try {
|
|
568
|
-
await this.close();
|
|
569
|
-
this.#stars = {};
|
|
570
|
-
this.#connections = {};
|
|
571
|
-
for (const star of this.starsConfig) {
|
|
572
|
-
try {
|
|
573
|
-
await this.setupStar(star);
|
|
574
|
-
}
|
|
575
|
-
catch (e) {
|
|
576
|
-
// If last star fails and none connected, surface error
|
|
577
|
-
if (Object.keys(this.#stars).length === 0)
|
|
578
|
-
throw new Error(`No star available to connect`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
finally {
|
|
583
|
-
debug('reinit: done');
|
|
584
|
-
this.#reinitLock = null;
|
|
585
|
-
}
|
|
586
|
-
})();
|
|
587
|
-
return this.#reinitLock;
|
|
588
|
-
}
|
|
589
|
-
async setupStar(star) {
|
|
590
|
-
const { retries, factor, minTimeout, maxTimeout } = this.#retryOptions;
|
|
591
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
592
|
-
let attempt = 0;
|
|
593
|
-
let lastErr;
|
|
594
|
-
while (attempt <= retries) {
|
|
595
|
-
try {
|
|
596
|
-
const client = new SocketRequestClient(star, this.networkVersion);
|
|
597
|
-
this.#stars[star] = await client.init();
|
|
598
|
-
this.setupStarListeners(this.#stars[star], star);
|
|
599
|
-
this.#stars[star].send({
|
|
600
|
-
url: 'join',
|
|
601
|
-
params: { version: this.version, peerId: this.peerId }
|
|
602
|
-
});
|
|
603
|
-
return this.#stars[star];
|
|
604
|
-
}
|
|
605
|
-
catch (e) {
|
|
606
|
-
lastErr = e;
|
|
607
|
-
attempt += 1;
|
|
608
|
-
if (attempt > retries)
|
|
609
|
-
break;
|
|
610
|
-
const delay = Math.min(maxTimeout, Math.round(minTimeout * Math.pow(factor, attempt - 1)));
|
|
611
|
-
debug(`setupStar ${star} failed, retrying in ${delay}ms (attempt ${attempt})`);
|
|
612
|
-
// eslint-disable-next-line no-await-in-loop
|
|
613
|
-
await sleep(delay);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
throw lastErr;
|
|
617
|
-
}
|
|
618
|
-
async _init() {
|
|
619
|
-
if (!globalThis.RTCPeerConnection)
|
|
620
|
-
globalThis.wrtc = (await import('./browser-Qcpp3EKK-DOtgsScX.js').then(function (n) { return n.b; })).default;
|
|
621
|
-
for (const star of this.starsConfig) {
|
|
622
|
-
try {
|
|
623
|
-
await this.setupStar(star);
|
|
624
|
-
}
|
|
625
|
-
catch (e) {
|
|
626
|
-
if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 &&
|
|
627
|
-
!this.socketClient)
|
|
628
|
-
throw new Error(`No star available to connect`);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
if (globalThis.process?.versions?.node) {
|
|
632
|
-
process.on('SIGINT', async () => {
|
|
633
|
-
process.stdin.resume();
|
|
634
|
-
await this.close();
|
|
635
|
-
process.exit();
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
globalThis.addEventListener('beforeunload', this.close.bind(this));
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
setupStarListeners(starConnection, starId) {
|
|
643
|
-
// create stable references to handlers so we can unsubscribe later
|
|
644
|
-
const onPeerJoined = (id) => this.#peerJoined(id, starConnection);
|
|
645
|
-
const onPeerLeft = (id) => this.#peerLeft(id, starConnection);
|
|
646
|
-
const onStarJoined = this.#starJoined;
|
|
647
|
-
const onStarLeft = this.#starLeft;
|
|
648
|
-
const onSignal = (message) => this.#inComingSignal(message, starConnection);
|
|
649
|
-
starConnection.pubsub.subscribe('peer:joined', onPeerJoined);
|
|
650
|
-
starConnection.pubsub.subscribe('peer:left', onPeerLeft);
|
|
651
|
-
starConnection.pubsub.subscribe('star:joined', onStarJoined);
|
|
652
|
-
starConnection.pubsub.subscribe('star:left', onStarLeft);
|
|
653
|
-
starConnection.pubsub.subscribe('signal', onSignal);
|
|
654
|
-
this.#starListeners[starId] = [
|
|
655
|
-
{ topic: 'peer:joined', handler: onPeerJoined },
|
|
656
|
-
{ topic: 'peer:left', handler: onPeerLeft },
|
|
657
|
-
{ topic: 'star:joined', handler: onStarJoined },
|
|
658
|
-
{ topic: 'star:left', handler: onStarLeft },
|
|
659
|
-
{ topic: 'signal', handler: onSignal }
|
|
660
|
-
];
|
|
661
|
-
}
|
|
662
|
-
#starJoined = (id) => {
|
|
663
|
-
if (this.#stars[id]) {
|
|
664
|
-
this.#stars[id].close(0);
|
|
665
|
-
delete this.#stars[id];
|
|
666
|
-
}
|
|
667
|
-
console.log(`star ${id} joined`);
|
|
668
|
-
};
|
|
669
|
-
#starLeft = async (id) => {
|
|
670
|
-
if (this.#stars[id]) {
|
|
671
|
-
this.#stars[id].close(0);
|
|
672
|
-
delete this.#stars[id];
|
|
673
|
-
}
|
|
674
|
-
// if we lost all stars, try to reconnect to configured stars with backoff
|
|
675
|
-
if (Object.keys(this.#stars).length === 0) {
|
|
676
|
-
for (const star of this.starsConfig) {
|
|
677
|
-
try {
|
|
678
|
-
await this.setupStar(star);
|
|
679
|
-
// stop at first success
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
catch (e) {
|
|
683
|
-
debug(`reconnect star ${star} failed: ${e.message || e}`);
|
|
684
|
-
if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1)
|
|
685
|
-
throw new Error(`No star available to connect`);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
debug(`star ${id} left`);
|
|
690
|
-
};
|
|
691
|
-
#peerLeft = (peer, star) => {
|
|
692
|
-
const id = peer.peerId || peer;
|
|
693
|
-
if (this.#connections[id]) {
|
|
694
|
-
this.#connections[id].destroy();
|
|
695
|
-
delete this.#connections[id];
|
|
696
|
-
}
|
|
697
|
-
debug(`peer ${id} left`);
|
|
698
|
-
};
|
|
699
|
-
connect(peerId, star, initiator = true) {
|
|
700
|
-
if (this.#connections[peerId]) {
|
|
701
|
-
debug(`peer ${peerId} already connected`);
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
if (this.#stars[star]?.connectionState() !== 'open') {
|
|
705
|
-
console.warn(`Star ${star} is not connected, cannot reconnect to peer ${peerId}`);
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
this.#createRTCPeerConnection(peerId, star, this.version, initiator);
|
|
709
|
-
}
|
|
710
|
-
reconnect(peerId, star, initiator = false) {
|
|
711
|
-
delete this.#connections[peerId];
|
|
712
|
-
debug(`reconnecting to peer ${peerId}`);
|
|
713
|
-
return this.connect(peerId, star, initiator);
|
|
714
|
-
}
|
|
715
|
-
#createRTCPeerConnection = (peerId, star, version, initiator = false) => {
|
|
716
|
-
const peer = new Peer({
|
|
717
|
-
initiator: initiator,
|
|
718
|
-
from: this.peerId,
|
|
719
|
-
to: peerId,
|
|
720
|
-
version
|
|
721
|
-
});
|
|
722
|
-
peer.on('signal', (signal) => this.#peerSignal(peer, signal, star, this.version));
|
|
723
|
-
peer.on('connect', () => this.#peerConnect(peer));
|
|
724
|
-
peer.on('close', () => this.#peerClose(peer));
|
|
725
|
-
peer.on('data', (data) => this.#peerData(peer, data));
|
|
726
|
-
peer.on('error', (error) => this.#peerError(peer, error));
|
|
727
|
-
this.#connections[peerId] = peer;
|
|
728
|
-
};
|
|
729
|
-
#peerJoined = async ({ peerId, version }, star) => {
|
|
730
|
-
// check if peer rejoined before the previous connection closed
|
|
731
|
-
if (this.#connections[peerId]) {
|
|
732
|
-
this.#connections[peerId].destroy();
|
|
733
|
-
delete this.#connections[peerId];
|
|
734
|
-
}
|
|
735
|
-
if (this.peerId !== peerId)
|
|
736
|
-
this.#createRTCPeerConnection(peerId, star, version, true);
|
|
737
|
-
debug(`peer ${peerId} joined`);
|
|
738
|
-
};
|
|
739
|
-
#inComingSignal = async ({ from, signal, channelName, version }, star) => {
|
|
740
|
-
if (version !== this.version) {
|
|
741
|
-
console.warn(`${from} joined using the wrong version.\nexpected: ${this.version} but got:${version}`);
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
if (from === this.peerId) {
|
|
745
|
-
console.warn(`${from} tried to connect to itself.`);
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
let peer = this.#connections[from];
|
|
749
|
-
if (!peer) {
|
|
750
|
-
this.#createRTCPeerConnection(from, star, version);
|
|
751
|
-
peer = this.#connections[from];
|
|
752
|
-
}
|
|
753
|
-
if (peer.connected) {
|
|
754
|
-
debug(`peer ${from} already connected`);
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
// peer.channels[channelName]
|
|
758
|
-
if (String(peer.channelName) !== String(channelName)) {
|
|
759
|
-
console.warn(`channelNames don't match: got ${peer.channelName}, expected: ${channelName}.`);
|
|
760
|
-
// Destroy the existing peer connection
|
|
761
|
-
// peer.destroy()
|
|
762
|
-
// delete this.#connections[from]
|
|
763
|
-
// // // Create a new peer connection with the correct configuration
|
|
764
|
-
// this.#createRTCPeerConnection(from, star, version, false)
|
|
765
|
-
// peer = this.#connections[from]
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
peer.signal(signal);
|
|
769
|
-
};
|
|
770
|
-
#peerSignal = (peer, signal, star, version) => {
|
|
771
|
-
let client = this.#stars[star];
|
|
772
|
-
if (!client)
|
|
773
|
-
client = this.#stars[Object.keys(this.#stars)[0]];
|
|
774
|
-
client.send({
|
|
775
|
-
url: 'signal',
|
|
776
|
-
params: {
|
|
777
|
-
from: this.peerId,
|
|
778
|
-
to: peer.peerId,
|
|
779
|
-
channelName: peer.channelName,
|
|
780
|
-
version,
|
|
781
|
-
signal,
|
|
782
|
-
initiator: peer.initiator
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
};
|
|
786
|
-
#peerClose = (peer) => {
|
|
787
|
-
if (this.#connections[peer.peerId]) {
|
|
788
|
-
peer.destroy();
|
|
789
|
-
delete this.#connections[peer.peerId];
|
|
790
|
-
}
|
|
791
|
-
debug(`closed ${peer.peerId}'s connection`);
|
|
792
|
-
};
|
|
793
|
-
#peerConnect = (peer) => {
|
|
794
|
-
debug(`${peer.peerId} connected`);
|
|
795
|
-
globalThis.pubsub.publishVerbose(this.#connectEvent, peer.peerId);
|
|
796
|
-
};
|
|
797
|
-
#noticeMessage = (message, id, from, peer) => {
|
|
798
|
-
const dataOut = message instanceof Uint8Array
|
|
799
|
-
? message
|
|
800
|
-
: new Uint8Array(Object.values(message));
|
|
801
|
-
if (globalThis.pubsub.subscribers[id]) {
|
|
802
|
-
globalThis.pubsub.publish(id, {
|
|
803
|
-
data: dataOut,
|
|
804
|
-
id,
|
|
805
|
-
from,
|
|
806
|
-
peer
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
else {
|
|
810
|
-
globalThis.pubsub.publish('peer:data', {
|
|
811
|
-
data: dataOut,
|
|
812
|
-
id,
|
|
813
|
-
from,
|
|
814
|
-
peer
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
|
-
};
|
|
818
|
-
#peerData = (peer, data) => {
|
|
819
|
-
const tryJson = () => {
|
|
820
|
-
const parsed = JSON.parse(new TextDecoder().decode(data));
|
|
821
|
-
const { id, size, chunk, index, count } = parsed;
|
|
822
|
-
chunk ? Object.values(chunk).length : size;
|
|
823
|
-
return {
|
|
824
|
-
id,
|
|
825
|
-
size: Number(size),
|
|
826
|
-
index: Number(index ?? 0),
|
|
827
|
-
count: Number(count ?? 1),
|
|
828
|
-
chunk: new Uint8Array(Object.values(chunk)),
|
|
829
|
-
flags: 0,
|
|
830
|
-
crc: 0
|
|
831
|
-
};
|
|
832
|
-
};
|
|
833
|
-
const decodeBinary = () => {
|
|
834
|
-
let u8;
|
|
835
|
-
if (typeof data === 'string') {
|
|
836
|
-
// should not happen when sending binary, fallback to JSON
|
|
837
|
-
return tryJson();
|
|
838
|
-
}
|
|
839
|
-
else if (data instanceof ArrayBuffer) {
|
|
840
|
-
u8 = new Uint8Array(data);
|
|
841
|
-
}
|
|
842
|
-
else if (ArrayBuffer.isView(data)) {
|
|
843
|
-
const view = data;
|
|
844
|
-
const byteOffset = view.byteOffset || 0;
|
|
845
|
-
const byteLength = view.byteLength || data.length;
|
|
846
|
-
u8 = new Uint8Array(view.buffer, byteOffset, byteLength);
|
|
847
|
-
}
|
|
848
|
-
else if (data?.buffer) {
|
|
849
|
-
u8 = new Uint8Array(data.buffer);
|
|
850
|
-
}
|
|
851
|
-
else {
|
|
852
|
-
// last resort: attempt JSON
|
|
853
|
-
return tryJson();
|
|
854
|
-
}
|
|
855
|
-
const dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
|
|
856
|
-
let offset = 0;
|
|
857
|
-
dv.getUint8(offset);
|
|
858
|
-
offset += 1;
|
|
859
|
-
const flags = dv.getUint8(offset);
|
|
860
|
-
offset += 1;
|
|
861
|
-
const size = dv.getUint32(offset, true);
|
|
862
|
-
offset += 4;
|
|
863
|
-
const index = dv.getUint32(offset, true);
|
|
864
|
-
offset += 4;
|
|
865
|
-
const count = dv.getUint32(offset, true);
|
|
866
|
-
offset += 4;
|
|
867
|
-
const expectedCrc = dv.getUint32(offset, true);
|
|
868
|
-
offset += 4;
|
|
869
|
-
const idLen = dv.getUint16(offset, true);
|
|
870
|
-
offset += 2;
|
|
871
|
-
const idBytes = u8.subarray(offset, offset + idLen);
|
|
872
|
-
offset += idLen;
|
|
873
|
-
const id = new TextDecoder().decode(idBytes);
|
|
874
|
-
const chunk = u8.subarray(offset);
|
|
875
|
-
return { id, size, index, count, chunk, flags, crc: expectedCrc };
|
|
876
|
-
};
|
|
877
|
-
const frame = decodeBinary();
|
|
878
|
-
peer.bw.down += frame.chunk.length;
|
|
879
|
-
// Single frame path: if compressed, inflate before publish
|
|
880
|
-
if (frame.count === 1) {
|
|
881
|
-
let payload = frame.chunk;
|
|
882
|
-
const compressed = Boolean(frame.flags & (1 << 1));
|
|
883
|
-
if (compressed) {
|
|
884
|
-
const actualCrc = crc32(payload);
|
|
885
|
-
if (actualCrc !== frame.crc) {
|
|
886
|
-
console.warn(`CRC mismatch: expected ${frame.crc}, got ${actualCrc}`);
|
|
887
|
-
}
|
|
888
|
-
try {
|
|
889
|
-
payload = inflate_1(payload);
|
|
890
|
-
}
|
|
891
|
-
catch (e) {
|
|
892
|
-
console.warn('inflate failed, passing compressed payload');
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
this.#noticeMessage(payload, frame.id, peer.peerId, peer);
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
// Chunked message handling with indexed reassembly
|
|
899
|
-
if (!this.#messagesToHandle[frame.id] ||
|
|
900
|
-
Array.isArray(this.#messagesToHandle[frame.id])) {
|
|
901
|
-
this.#messagesToHandle[frame.id] = {
|
|
902
|
-
chunks: new Array(frame.count),
|
|
903
|
-
receivedBytes: 0,
|
|
904
|
-
expectedSize: Number(frame.size),
|
|
905
|
-
expectedCount: Number(frame.count)
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
const state = this.#messagesToHandle[frame.id];
|
|
909
|
-
// Verify CRC for this chunk
|
|
910
|
-
const actualCrc = crc32(frame.chunk);
|
|
911
|
-
if (actualCrc !== frame.crc) {
|
|
912
|
-
console.warn(`Chunk CRC mismatch for ${frame.id}[${frame.index}]: expected ${frame.crc}, got ${actualCrc}`);
|
|
913
|
-
}
|
|
914
|
-
state.chunks[frame.index] = frame.chunk;
|
|
915
|
-
state.receivedBytes += frame.chunk.length;
|
|
916
|
-
// If all chunks present and total size matches, reassemble
|
|
917
|
-
const allPresent = state.chunks.every((c) => c instanceof Uint8Array);
|
|
918
|
-
if (allPresent && state.receivedBytes === state.expectedSize) {
|
|
919
|
-
const result = new Uint8Array(state.expectedSize);
|
|
920
|
-
let offset2 = 0;
|
|
921
|
-
for (const c of state.chunks) {
|
|
922
|
-
result.set(c, offset2);
|
|
923
|
-
offset2 += c.length;
|
|
924
|
-
}
|
|
925
|
-
let payload = result;
|
|
926
|
-
const compressed = Boolean(frame.flags & (1 << 1));
|
|
927
|
-
if (compressed) {
|
|
928
|
-
try {
|
|
929
|
-
payload = inflate_1(result);
|
|
930
|
-
}
|
|
931
|
-
catch (e) {
|
|
932
|
-
console.warn('inflate failed, passing compressed payload');
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
this.#noticeMessage(payload, frame.id, peer.peerId, peer);
|
|
936
|
-
delete this.#messagesToHandle[frame.id];
|
|
937
|
-
}
|
|
938
|
-
};
|
|
939
|
-
#peerError = (peer, error) => {
|
|
940
|
-
console.warn(`Connection error: ${error.message}`);
|
|
941
|
-
peer.destroy();
|
|
942
|
-
};
|
|
943
|
-
async close() {
|
|
944
|
-
for (const peerId in this.#connections) {
|
|
945
|
-
const peer = this.#connections[peerId];
|
|
946
|
-
if (peer) {
|
|
947
|
-
peer.destroy();
|
|
948
|
-
delete this.#connections[peerId];
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
for (const star in this.#stars) {
|
|
952
|
-
// unsubscribe handlers we registered earlier
|
|
953
|
-
const listeners = this.#starListeners[star];
|
|
954
|
-
if (listeners && listeners.length) {
|
|
955
|
-
for (const { topic, handler } of listeners) {
|
|
956
|
-
try {
|
|
957
|
-
this.#stars[star].pubsub.unsubscribe(topic, handler);
|
|
958
|
-
}
|
|
959
|
-
catch (e) {
|
|
960
|
-
// ignore
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
if (this.#stars[star].connectionState() === 'open') {
|
|
965
|
-
await this.#stars[star].send({ url: 'leave', params: this.peerId });
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
const peerClosers = Object.values(this.#connections).map((connection) => {
|
|
969
|
-
try {
|
|
970
|
-
// destroy() may be sync or return a promise
|
|
971
|
-
return connection.destroy();
|
|
972
|
-
}
|
|
973
|
-
catch (e) {
|
|
974
|
-
return undefined;
|
|
975
|
-
}
|
|
976
|
-
});
|
|
977
|
-
const starClosers = Object.values(this.#stars).map((connection) => {
|
|
978
|
-
try {
|
|
979
|
-
return connection.close(0);
|
|
980
|
-
}
|
|
981
|
-
catch (e) {
|
|
982
|
-
return undefined;
|
|
983
|
-
}
|
|
984
|
-
});
|
|
985
|
-
return Promise.allSettled([...peerClosers, ...starClosers]);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
export { Client as default };
|