@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.
@@ -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 };