@leofcoin/chain 1.7.98 → 1.7.100
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 +241 -40
- package/exports/browser/{client-BDXkFVRR-BZjYAtds.js → client-UwSHOtRi-BsddY8I0.js} +181 -29
- package/exports/browser/{messages-DDEOxfJp-CggxH-2R.js → messages-CFqwXbrQ-D0JHZ5TG.js} +1 -1
- package/exports/browser/{node-browser-D0JliYju.js → node-browser-DPfaEz6Y.js} +2 -2
- package/exports/browser/node-browser.js +1 -1
- package/exports/chain.js +241 -40
- package/package.json +2 -2
package/exports/browser/chain.js
CHANGED
|
@@ -5317,6 +5317,10 @@ class ConnectionMonitor {
|
|
|
5317
5317
|
#reconnectDelay = 5000;
|
|
5318
5318
|
#healthCheckInterval = 10000;
|
|
5319
5319
|
#version;
|
|
5320
|
+
// event handlers to remove later
|
|
5321
|
+
#onOnline = null;
|
|
5322
|
+
#onVisibilityChange = null;
|
|
5323
|
+
#onSigcont = null;
|
|
5320
5324
|
get isMonitoring() {
|
|
5321
5325
|
return this.#isMonitoring;
|
|
5322
5326
|
}
|
|
@@ -5336,6 +5340,33 @@ class ConnectionMonitor {
|
|
|
5336
5340
|
return;
|
|
5337
5341
|
this.#isMonitoring = true;
|
|
5338
5342
|
console.log('🔄 Starting connection monitor...');
|
|
5343
|
+
// Listen for resume/network events (browser + node/electron)
|
|
5344
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
5345
|
+
this.#onOnline = () => {
|
|
5346
|
+
console.log('🌐 Network online — attempting restore');
|
|
5347
|
+
void this.#restoreNetwork();
|
|
5348
|
+
};
|
|
5349
|
+
window.addEventListener('online', this.#onOnline);
|
|
5350
|
+
this.#onVisibilityChange = () => {
|
|
5351
|
+
if (document.visibilityState === 'visible') {
|
|
5352
|
+
console.log('💡 Visibility regained — attempting restore');
|
|
5353
|
+
void this.#restoreNetwork();
|
|
5354
|
+
}
|
|
5355
|
+
};
|
|
5356
|
+
document.addEventListener('visibilitychange', this.#onVisibilityChange);
|
|
5357
|
+
}
|
|
5358
|
+
if (typeof process !== 'undefined' && typeof process.on === 'function') {
|
|
5359
|
+
this.#onSigcont = () => {
|
|
5360
|
+
console.log('🔔 Process resumed (SIGCONT) — attempting restore');
|
|
5361
|
+
void this.#restoreNetwork();
|
|
5362
|
+
};
|
|
5363
|
+
try {
|
|
5364
|
+
process.on('SIGCONT', this.#onSigcont);
|
|
5365
|
+
}
|
|
5366
|
+
catch (e) {
|
|
5367
|
+
// ignore if not supported
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5339
5370
|
this.#checkInterval = setInterval(() => {
|
|
5340
5371
|
this.#healthCheck();
|
|
5341
5372
|
}, this.#healthCheckInterval);
|
|
@@ -5350,6 +5381,26 @@ class ConnectionMonitor {
|
|
|
5350
5381
|
clearInterval(this.#checkInterval);
|
|
5351
5382
|
this.#checkInterval = null;
|
|
5352
5383
|
}
|
|
5384
|
+
// remove listeners
|
|
5385
|
+
if (typeof window !== 'undefined') {
|
|
5386
|
+
if (this.#onOnline) {
|
|
5387
|
+
window.removeEventListener('online', this.#onOnline);
|
|
5388
|
+
this.#onOnline = null;
|
|
5389
|
+
}
|
|
5390
|
+
if (this.#onVisibilityChange) {
|
|
5391
|
+
document.removeEventListener('visibilitychange', this.#onVisibilityChange);
|
|
5392
|
+
this.#onVisibilityChange = null;
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
if (typeof process !== 'undefined' && typeof process.removeListener === 'function' && this.#onSigcont) {
|
|
5396
|
+
try {
|
|
5397
|
+
process.removeListener('SIGCONT', this.#onSigcont);
|
|
5398
|
+
}
|
|
5399
|
+
catch (e) {
|
|
5400
|
+
// ignore
|
|
5401
|
+
}
|
|
5402
|
+
this.#onSigcont = null;
|
|
5403
|
+
}
|
|
5353
5404
|
console.log('⏹️ Connection monitor stopped');
|
|
5354
5405
|
}
|
|
5355
5406
|
async #healthCheck() {
|
|
@@ -5369,12 +5420,14 @@ class ConnectionMonitor {
|
|
|
5369
5420
|
const disconnectedPeers = this.disconnectedPeers;
|
|
5370
5421
|
if (disconnectedPeers.length > 0) {
|
|
5371
5422
|
console.warn(`⚠️ Disconnected peers: ${disconnectedPeers.map((peer) => peer.peerId).join(', ')}`);
|
|
5372
|
-
// Attempt to reconnect each disconnected peer
|
|
5373
|
-
const promises = [];
|
|
5423
|
+
// Attempt to reconnect each disconnected peer sequentially to avoid racing signaling/state
|
|
5374
5424
|
for (const peer of disconnectedPeers) {
|
|
5375
|
-
|
|
5425
|
+
// small spacing between attempts to reduce signaling races
|
|
5426
|
+
// eslint-disable-next-line no-await-in-loop
|
|
5427
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
5428
|
+
// eslint-disable-next-line no-await-in-loop
|
|
5429
|
+
await this.#attemptPeerReconnection(peer);
|
|
5376
5430
|
}
|
|
5377
|
-
await Promise.all(promises);
|
|
5378
5431
|
}
|
|
5379
5432
|
// Publish connection status
|
|
5380
5433
|
globalThis.pubsub?.publish('connection-status', {
|
|
@@ -5384,54 +5437,169 @@ class ConnectionMonitor {
|
|
|
5384
5437
|
});
|
|
5385
5438
|
}
|
|
5386
5439
|
async #attemptPeerReconnection(peer) {
|
|
5387
|
-
if (
|
|
5388
|
-
|
|
5389
|
-
|
|
5440
|
+
if (!peer)
|
|
5441
|
+
return;
|
|
5442
|
+
const peerId = peer.peerId || peer.id;
|
|
5443
|
+
if (!peerId)
|
|
5444
|
+
return;
|
|
5445
|
+
if (!this.#peerReconnectAttempts[peerId]) {
|
|
5446
|
+
this.#peerReconnectAttempts[peerId] = 0;
|
|
5390
5447
|
}
|
|
5391
|
-
if (
|
|
5392
|
-
|
|
5448
|
+
if (this.#peerReconnectAttempts[peerId] >= this.#maxReconnectAttempts) {
|
|
5449
|
+
console.error('❌ Max reconnection attempts reached for', peerId);
|
|
5450
|
+
this.#peerReconnectAttempts[peerId] = 0;
|
|
5451
|
+
return;
|
|
5393
5452
|
}
|
|
5394
|
-
this.#peerReconnectAttempts[
|
|
5395
|
-
console.log(`🔄 Attempting reconnection ${this.#peerReconnectAttempts[
|
|
5453
|
+
this.#peerReconnectAttempts[peerId]++;
|
|
5454
|
+
console.log(`🔄 Attempting reconnection ${this.#peerReconnectAttempts[peerId]}/${this.#maxReconnectAttempts} for ${peerId}`);
|
|
5396
5455
|
try {
|
|
5397
|
-
const
|
|
5398
|
-
|
|
5399
|
-
|
|
5456
|
+
const peernet = globalThis.peernet;
|
|
5457
|
+
if (!peernet) {
|
|
5458
|
+
console.warn('⚠️ globalThis.peernet not available');
|
|
5459
|
+
return;
|
|
5460
|
+
}
|
|
5461
|
+
// Try targeted reconnect if available
|
|
5462
|
+
if (peernet.client?.reconnect) {
|
|
5463
|
+
try {
|
|
5464
|
+
await peernet.client.reconnect(peerId, peernet.stars?.[0]);
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5467
|
+
catch (err) {
|
|
5468
|
+
const msg = String(err?.message || err);
|
|
5469
|
+
console.warn('⚠️ Targeted reconnect failed:', msg);
|
|
5470
|
+
// handle signaling/state mismatches by cleaning up only that peer and retrying targeted reconnect
|
|
5471
|
+
if (msg.includes('Called in wrong state') ||
|
|
5472
|
+
msg.includes('setRemoteDescription') ||
|
|
5473
|
+
msg.includes('channelNames') ||
|
|
5474
|
+
msg.includes("channelNames don't match")) {
|
|
5475
|
+
console.warn('⚠️ Detected signaling/channel mismatch — cleaning up peer state and retrying targeted reconnect');
|
|
5476
|
+
try {
|
|
5477
|
+
await this.#cleanupPeerState(peerId, peernet);
|
|
5478
|
+
// small backoff before retry
|
|
5479
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
5480
|
+
await peernet.client.reconnect(peerId, peernet.stars?.[0]);
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
catch (retryErr) {
|
|
5484
|
+
console.warn('⚠️ Retry targeted reconnect failed:', String(retryErr?.message || retryErr));
|
|
5485
|
+
// fall through to non-targeted fallback below
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
throw err;
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
// If no targeted reconnect, try start/restore
|
|
5492
|
+
if (peernet.start) {
|
|
5493
|
+
await peernet.start();
|
|
5494
|
+
}
|
|
5400
5495
|
}
|
|
5401
5496
|
catch (error) {
|
|
5402
|
-
console.error('❌ Reconnection failed:', error
|
|
5497
|
+
console.error('❌ Reconnection failed:', error?.message || error);
|
|
5498
|
+
// As fallback, try full restart only if even the cleanup+retry failed
|
|
5499
|
+
if (globalThis.peernet) {
|
|
5500
|
+
await this.#performFullRestart(globalThis.peernet);
|
|
5501
|
+
}
|
|
5403
5502
|
}
|
|
5404
|
-
// // Try to restart the network
|
|
5405
|
-
// if (globalThis.peernet?.start) {
|
|
5406
|
-
// await globalThis.peernet.start()
|
|
5407
|
-
// } else {
|
|
5408
|
-
// console.warn('⚠️ Peernet start method not available, skipping reconnection')
|
|
5409
|
-
// }
|
|
5410
|
-
// } catch (error) {
|
|
5411
|
-
// console.error('❌ Reconnection failed:', error.message)
|
|
5412
|
-
// }
|
|
5413
5503
|
}
|
|
5414
|
-
|
|
5415
|
-
|
|
5504
|
+
// helper: try to close/destroy a single peer connection and remove it from peernet's map
|
|
5505
|
+
async #cleanupPeerState(peerId, peernet) {
|
|
5416
5506
|
try {
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
//
|
|
5422
|
-
|
|
5507
|
+
const conns = peernet.connections || {};
|
|
5508
|
+
const conn = conns[peerId] || conns[Object.keys(conns).find((k) => k.includes(peerId) || peerId.includes(k))];
|
|
5509
|
+
if (!conn)
|
|
5510
|
+
return;
|
|
5511
|
+
// close underlying RTCPeerConnection if exposed
|
|
5512
|
+
try {
|
|
5513
|
+
if (conn.pc && typeof conn.pc.close === 'function') {
|
|
5514
|
+
conn.pc.close();
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
catch (e) {
|
|
5518
|
+
// ignore
|
|
5519
|
+
}
|
|
5520
|
+
// call any destroy/cleanup API on the connection object
|
|
5521
|
+
try {
|
|
5522
|
+
if (typeof conn.destroy === 'function') {
|
|
5523
|
+
conn.destroy();
|
|
5524
|
+
}
|
|
5525
|
+
else if (typeof conn.close === 'function') {
|
|
5526
|
+
conn.close();
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5529
|
+
catch (e) {
|
|
5530
|
+
// ignore
|
|
5531
|
+
}
|
|
5532
|
+
// remove reference so reconnect path will create a fresh one
|
|
5533
|
+
try {
|
|
5534
|
+
delete peernet.connections[peerId];
|
|
5535
|
+
}
|
|
5536
|
+
catch (e) {
|
|
5537
|
+
// ignore
|
|
5538
|
+
}
|
|
5539
|
+
// small pause to let underlying sockets/RTCs settle
|
|
5540
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
5423
5541
|
}
|
|
5424
|
-
catch (
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5542
|
+
catch (e) {
|
|
5543
|
+
// ignore cleanup errors
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
// New helper: close stale RTCPeerConnections if present and then restart peernet
|
|
5547
|
+
async #performFullRestart(peernet) {
|
|
5548
|
+
try {
|
|
5549
|
+
// Close underlying peer RTCPeerConnections if the library exposes them
|
|
5550
|
+
try {
|
|
5551
|
+
const conns = peernet.connections || {};
|
|
5552
|
+
for (const id of Object.keys(conns)) {
|
|
5553
|
+
const p = conns[id];
|
|
5554
|
+
// try to close underlying RTCPeerConnection if exposed
|
|
5555
|
+
if (p && p.pc && typeof p.pc.close === 'function') {
|
|
5556
|
+
try {
|
|
5557
|
+
p.pc.close();
|
|
5558
|
+
}
|
|
5559
|
+
catch (e) {
|
|
5560
|
+
// ignore
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
catch (e) {
|
|
5566
|
+
// ignore
|
|
5567
|
+
}
|
|
5568
|
+
// If the library supports stop -> start, do that to fully reset signaling state
|
|
5569
|
+
if (typeof peernet.stop === 'function') {
|
|
5570
|
+
try {
|
|
5571
|
+
await peernet.stop();
|
|
5572
|
+
}
|
|
5573
|
+
catch (e) {
|
|
5574
|
+
// ignore stop errors
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
// small delay to ensure sockets/RTCs are closed
|
|
5578
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
5579
|
+
if (typeof peernet.start === 'function') {
|
|
5580
|
+
await peernet.start();
|
|
5429
5581
|
}
|
|
5430
5582
|
else {
|
|
5431
|
-
|
|
5432
|
-
this.#reconnectDelay = Math.min(this.#reconnectDelay * 1.5, 30000);
|
|
5433
|
-
console.warn(`⚠️ Increasing reconnection delay to ${this.#reconnectDelay} ms`);
|
|
5583
|
+
console.warn('⚠️ peernet.start not available for full restart');
|
|
5434
5584
|
}
|
|
5585
|
+
// reset reconnect attempts so we can try fresh
|
|
5586
|
+
this.#peerReconnectAttempts = {};
|
|
5587
|
+
console.log('✅ Full peernet restart completed');
|
|
5588
|
+
}
|
|
5589
|
+
catch (e) {
|
|
5590
|
+
console.error('❌ Full restart failed:', e?.message || e);
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
// Called on visibility/online/resume events
|
|
5594
|
+
async #restoreNetwork() {
|
|
5595
|
+
console.log('🔁 Restoring network after resume/wake');
|
|
5596
|
+
// If there is a peernet instance, try a safe restore
|
|
5597
|
+
if (globalThis.peernet) {
|
|
5598
|
+
await this.#performFullRestart(globalThis.peernet);
|
|
5599
|
+
}
|
|
5600
|
+
else {
|
|
5601
|
+
// If no global peernet, attempt a normal reconnection flow
|
|
5602
|
+
await this.#attemptReconnection();
|
|
5435
5603
|
}
|
|
5436
5604
|
}
|
|
5437
5605
|
async waitForPeers(timeoutMs = 30000) {
|
|
@@ -5451,6 +5619,39 @@ class ConnectionMonitor {
|
|
|
5451
5619
|
checkPeers();
|
|
5452
5620
|
});
|
|
5453
5621
|
}
|
|
5622
|
+
// New: attempt reconnection flow (gentle start + sequential per-peer reconnect)
|
|
5623
|
+
async #attemptReconnection() {
|
|
5624
|
+
console.warn('⚠️ Attempting to reconnect to peers...');
|
|
5625
|
+
try {
|
|
5626
|
+
// gentle restore: if peernet supports start(), try that first
|
|
5627
|
+
if (globalThis.peernet?.start) {
|
|
5628
|
+
await globalThis.peernet.start();
|
|
5629
|
+
}
|
|
5630
|
+
// attempt targeted reconnection for disconnected peers sequentially (avoid racing WebRTC state)
|
|
5631
|
+
const disconnected = this.disconnectedPeers;
|
|
5632
|
+
for (const p of disconnected) {
|
|
5633
|
+
// small spacing between attempts to reduce signaling races
|
|
5634
|
+
// eslint-disable-next-line no-await-in-loop
|
|
5635
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
5636
|
+
// eslint-disable-next-line no-await-in-loop
|
|
5637
|
+
await this.#attemptPeerReconnection(p);
|
|
5638
|
+
}
|
|
5639
|
+
// pause before next health check cycle
|
|
5640
|
+
await new Promise((resolve) => setTimeout(resolve, this.#reconnectDelay));
|
|
5641
|
+
}
|
|
5642
|
+
catch (error) {
|
|
5643
|
+
console.error('❌ Reconnection failed:', error?.message || error);
|
|
5644
|
+
if (this.#reconnectDelay >= 30000) {
|
|
5645
|
+
console.warn('⚠️ Reconnection delay reached maximum, resetting to 5 seconds');
|
|
5646
|
+
this.#reconnectDelay = 5000;
|
|
5647
|
+
}
|
|
5648
|
+
else {
|
|
5649
|
+
// exponential-ish backoff
|
|
5650
|
+
this.#reconnectDelay = Math.min(this.#reconnectDelay * 1.5, 30000);
|
|
5651
|
+
console.warn(`⚠️ Increasing reconnection delay to ${this.#reconnectDelay} ms`);
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5454
5655
|
}
|
|
5455
5656
|
|
|
5456
5657
|
const debug = globalThis.createDebugger('leofcoin/chain');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LittlePubSub } from './node-browser-
|
|
1
|
+
import { L as LittlePubSub } from './node-browser-DPfaEz6Y.js';
|
|
2
2
|
import './identity-Cn0iQbY3-CeW0giQS.js';
|
|
3
3
|
import './index-DUfUgiQY.js';
|
|
4
4
|
|
|
@@ -428,7 +428,11 @@ class Client {
|
|
|
428
428
|
#peerId;
|
|
429
429
|
#connections = {};
|
|
430
430
|
#stars = {};
|
|
431
|
+
#starListeners = {};
|
|
432
|
+
#handlersSetup = false;
|
|
433
|
+
#reinitLock = null;
|
|
431
434
|
#connectEvent = 'peer:connected';
|
|
435
|
+
#retryOptions = { retries: 5, factor: 2, minTimeout: 1000, maxTimeout: 30000 };
|
|
432
436
|
id;
|
|
433
437
|
networkVersion;
|
|
434
438
|
starsConfig;
|
|
@@ -467,21 +471,80 @@ class Client {
|
|
|
467
471
|
this.version = version;
|
|
468
472
|
this.#connectEvent = connectEvent;
|
|
469
473
|
this.starsConfig = stars;
|
|
474
|
+
if (options?.retry)
|
|
475
|
+
this.#retryOptions = { ...this.#retryOptions, ...options.retry };
|
|
470
476
|
this._init();
|
|
471
477
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Safely reinitialize the client (used after system resume/sleep).
|
|
480
|
+
* It closes existing connections and reconnects to configured stars.
|
|
481
|
+
*/
|
|
482
|
+
async reinit() {
|
|
483
|
+
// avoid concurrent reinit runs
|
|
484
|
+
if (this.#reinitLock)
|
|
485
|
+
return this.#reinitLock;
|
|
486
|
+
this.#reinitLock = (async () => {
|
|
487
|
+
debug('reinit: start');
|
|
488
|
+
try {
|
|
489
|
+
await this.close();
|
|
490
|
+
// clear internal maps so setupStar starts fresh
|
|
491
|
+
this.#stars = {};
|
|
492
|
+
this.#connections = {};
|
|
493
|
+
for (const star of this.starsConfig) {
|
|
494
|
+
try {
|
|
495
|
+
await this.setupStar(star);
|
|
496
|
+
}
|
|
497
|
+
catch (e) {
|
|
498
|
+
// If last star fails and none connected, surface error
|
|
499
|
+
if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 &&
|
|
500
|
+
Object.keys(this.#stars).length === 0)
|
|
501
|
+
throw new Error(`No star available to connect`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
finally {
|
|
506
|
+
debug('reinit: done');
|
|
507
|
+
this.#reinitLock = null;
|
|
508
|
+
}
|
|
509
|
+
})();
|
|
510
|
+
return this.#reinitLock;
|
|
511
|
+
}
|
|
512
|
+
async setupStar(star) {
|
|
513
|
+
const { retries, factor, minTimeout, maxTimeout } = this.#retryOptions;
|
|
514
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
515
|
+
let attempt = 0;
|
|
516
|
+
let lastErr;
|
|
517
|
+
while (attempt <= retries) {
|
|
477
518
|
try {
|
|
478
519
|
const client = new SocketRequestClient(star, this.networkVersion);
|
|
479
520
|
this.#stars[star] = await client.init();
|
|
480
|
-
this.setupStarListeners(this.#stars[star]);
|
|
521
|
+
this.setupStarListeners(this.#stars[star], star);
|
|
481
522
|
this.#stars[star].send({
|
|
482
523
|
url: 'join',
|
|
483
524
|
params: { version: this.version, peerId: this.peerId }
|
|
484
525
|
});
|
|
526
|
+
return this.#stars[star];
|
|
527
|
+
}
|
|
528
|
+
catch (e) {
|
|
529
|
+
lastErr = e;
|
|
530
|
+
attempt += 1;
|
|
531
|
+
if (attempt > retries)
|
|
532
|
+
break;
|
|
533
|
+
const delay = Math.min(maxTimeout, Math.round(minTimeout * Math.pow(factor, attempt - 1)));
|
|
534
|
+
debug(`setupStar ${star} failed, retrying in ${delay}ms (attempt ${attempt})`);
|
|
535
|
+
// eslint-disable-next-line no-await-in-loop
|
|
536
|
+
await sleep(delay);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
throw lastErr;
|
|
540
|
+
}
|
|
541
|
+
async _init() {
|
|
542
|
+
// reconnectJob()
|
|
543
|
+
if (!globalThis.RTCPeerConnection)
|
|
544
|
+
globalThis.wrtc = (await import('./browser-Cjcx-T47-D0KbGYCz.js').then(function (n) { return n.b; })).default;
|
|
545
|
+
for (const star of this.starsConfig) {
|
|
546
|
+
try {
|
|
547
|
+
await this.setupStar(star);
|
|
485
548
|
}
|
|
486
549
|
catch (e) {
|
|
487
550
|
if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 &&
|
|
@@ -499,13 +562,68 @@ class Client {
|
|
|
499
562
|
else {
|
|
500
563
|
globalThis.addEventListener('beforeunload', this.close.bind(this));
|
|
501
564
|
}
|
|
565
|
+
// Setup resume/sleep detection so we can reinit connections after wake
|
|
566
|
+
this._setupResumeHandler();
|
|
567
|
+
}
|
|
568
|
+
setupStarListeners(starConnection, starId) {
|
|
569
|
+
// create stable references to handlers so we can unsubscribe later
|
|
570
|
+
const onPeerJoined = (id) => this.#peerJoined(id, starConnection);
|
|
571
|
+
const onPeerLeft = (id) => this.#peerLeft(id, starConnection);
|
|
572
|
+
const onStarJoined = this.#starJoined;
|
|
573
|
+
const onStarLeft = this.#starLeft;
|
|
574
|
+
const onSignal = (message) => this.#inComingSignal(message, starConnection);
|
|
575
|
+
starConnection.pubsub.subscribe('peer:joined', onPeerJoined);
|
|
576
|
+
starConnection.pubsub.subscribe('peer:left', onPeerLeft);
|
|
577
|
+
starConnection.pubsub.subscribe('star:joined', onStarJoined);
|
|
578
|
+
starConnection.pubsub.subscribe('star:left', onStarLeft);
|
|
579
|
+
starConnection.pubsub.subscribe('signal', onSignal);
|
|
580
|
+
this.#starListeners[starId] = [
|
|
581
|
+
{ topic: 'peer:joined', handler: onPeerJoined },
|
|
582
|
+
{ topic: 'peer:left', handler: onPeerLeft },
|
|
583
|
+
{ topic: 'star:joined', handler: onStarJoined },
|
|
584
|
+
{ topic: 'star:left', handler: onStarLeft },
|
|
585
|
+
{ topic: 'signal', handler: onSignal }
|
|
586
|
+
];
|
|
502
587
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
588
|
+
_setupResumeHandler() {
|
|
589
|
+
if (this.#handlersSetup)
|
|
590
|
+
return;
|
|
591
|
+
this.#handlersSetup = true;
|
|
592
|
+
const THRESHOLD = 10 * 1000; // 10s gap indicates sleep/wake
|
|
593
|
+
let last = Date.now();
|
|
594
|
+
const check = () => {
|
|
595
|
+
const now = Date.now();
|
|
596
|
+
const delta = now - last;
|
|
597
|
+
last = now;
|
|
598
|
+
if (delta > THRESHOLD) {
|
|
599
|
+
debug(`resume detected (gap ${delta}ms)`);
|
|
600
|
+
this.reinit().catch((e) => debug('reinit error', e));
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
// Start interval checker
|
|
604
|
+
const iv = setInterval(check, 2000);
|
|
605
|
+
// Browser specific events
|
|
606
|
+
if (typeof document !== 'undefined' && document.addEventListener) {
|
|
607
|
+
document.addEventListener('visibilitychange', () => {
|
|
608
|
+
if (document.visibilityState === 'visible') {
|
|
609
|
+
// small delay to let timers update
|
|
610
|
+
setTimeout(() => check(), 50);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
window.addEventListener('online', () => setTimeout(() => check(), 50));
|
|
614
|
+
}
|
|
615
|
+
// Node: listen for SIGCONT (process continued) as well
|
|
616
|
+
if (globalThis.process?.on) {
|
|
617
|
+
try {
|
|
618
|
+
process.on('SIGCONT', () => setTimeout(() => check(), 50));
|
|
619
|
+
}
|
|
620
|
+
catch (e) {
|
|
621
|
+
// ignore
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
// keep reference so it can be cleared on close
|
|
625
|
+
// @ts-ignore
|
|
626
|
+
this._resumeInterval = iv;
|
|
509
627
|
}
|
|
510
628
|
#starJoined = (id) => {
|
|
511
629
|
if (this.#stars[id]) {
|
|
@@ -519,20 +637,16 @@ class Client {
|
|
|
519
637
|
this.#stars[id].close(0);
|
|
520
638
|
delete this.#stars[id];
|
|
521
639
|
}
|
|
640
|
+
// if we lost all stars, try to reconnect to configured stars with backoff
|
|
522
641
|
if (Object.keys(this.#stars).length === 0) {
|
|
523
642
|
for (const star of this.starsConfig) {
|
|
524
643
|
try {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
this.#stars[star] = socketClient;
|
|
529
|
-
this.#stars[star].send({
|
|
530
|
-
url: 'join',
|
|
531
|
-
params: { peerId: this.peerId, version: this.version }
|
|
532
|
-
});
|
|
533
|
-
this.setupStarListeners(socketClient);
|
|
644
|
+
await this.setupStar(star);
|
|
645
|
+
// stop at first success
|
|
646
|
+
return;
|
|
534
647
|
}
|
|
535
648
|
catch (e) {
|
|
649
|
+
debug(`reconnect star ${star} failed: ${e.message || e}`);
|
|
536
650
|
if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1)
|
|
537
651
|
throw new Error(`No star available to connect`);
|
|
538
652
|
}
|
|
@@ -611,7 +725,7 @@ class Client {
|
|
|
611
725
|
// Destroy the existing peer connection
|
|
612
726
|
// peer.destroy()
|
|
613
727
|
// delete this.#connections[from]
|
|
614
|
-
// // Create a new peer connection with the correct configuration
|
|
728
|
+
// // // Create a new peer connection with the correct configuration
|
|
615
729
|
// this.#createRTCPeerConnection(from, star, version, false)
|
|
616
730
|
// peer = this.#connections[from]
|
|
617
731
|
}
|
|
@@ -685,15 +799,53 @@ class Client {
|
|
|
685
799
|
peer.destroy();
|
|
686
800
|
};
|
|
687
801
|
async close() {
|
|
802
|
+
// clear resume interval if set
|
|
803
|
+
// @ts-ignore
|
|
804
|
+
if (this._resumeInterval) {
|
|
805
|
+
// @ts-ignore
|
|
806
|
+
clearInterval(this._resumeInterval);
|
|
807
|
+
// @ts-ignore
|
|
808
|
+
this._resumeInterval = null;
|
|
809
|
+
}
|
|
688
810
|
for (const star in this.#stars) {
|
|
689
|
-
if (this.#stars[star].connectionState() === 'open')
|
|
811
|
+
if (this.#stars[star].connectionState() === 'open') {
|
|
690
812
|
await this.#stars[star].send({ url: 'leave', params: this.peerId });
|
|
813
|
+
// unsubscribe handlers we registered earlier
|
|
814
|
+
const listeners = this.#starListeners[star];
|
|
815
|
+
if (listeners && listeners.length) {
|
|
816
|
+
for (const { topic, handler } of listeners) {
|
|
817
|
+
try {
|
|
818
|
+
this.#stars[star].pubsub.unsubscribe(topic, handler);
|
|
819
|
+
}
|
|
820
|
+
catch (e) {
|
|
821
|
+
// ignore
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
691
826
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
827
|
+
// Ensure we wait for all peer and star close/destroy operations.
|
|
828
|
+
// Previous code passed an array of arrays to Promise.allSettled which
|
|
829
|
+
// resolves immediately; flatten into a single array of promises (or
|
|
830
|
+
// values) so we actually wait for async close operations.
|
|
831
|
+
const peerClosers = Object.values(this.#connections).map((connection) => {
|
|
832
|
+
try {
|
|
833
|
+
// destroy() may be sync or return a promise
|
|
834
|
+
return connection.destroy();
|
|
835
|
+
}
|
|
836
|
+
catch (e) {
|
|
837
|
+
return undefined;
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
const starClosers = Object.values(this.#stars).map((connection) => {
|
|
841
|
+
try {
|
|
842
|
+
return connection.close(0);
|
|
843
|
+
}
|
|
844
|
+
catch (e) {
|
|
845
|
+
return undefined;
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
return Promise.allSettled([...peerClosers, ...starClosers]);
|
|
697
849
|
}
|
|
698
850
|
}
|
|
699
851
|
|
|
@@ -8491,7 +8491,7 @@ class Peernet {
|
|
|
8491
8491
|
this.root = options.root;
|
|
8492
8492
|
const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile
|
|
8493
8493
|
// FolderMessageResponse
|
|
8494
|
-
} = await import(/* webpackChunkName: "messages" */ './messages-
|
|
8494
|
+
} = await import(/* webpackChunkName: "messages" */ './messages-CFqwXbrQ-D0JHZ5TG.js');
|
|
8495
8495
|
/**
|
|
8496
8496
|
* proto Object containing protos
|
|
8497
8497
|
* @type {Object}
|
|
@@ -8585,7 +8585,7 @@ class Peernet {
|
|
|
8585
8585
|
if (this.#starting || this.#started)
|
|
8586
8586
|
return;
|
|
8587
8587
|
this.#starting = true;
|
|
8588
|
-
const importee = await import('./client-
|
|
8588
|
+
const importee = await import('./client-UwSHOtRi-BsddY8I0.js');
|
|
8589
8589
|
/**
|
|
8590
8590
|
* @access public
|
|
8591
8591
|
* @type {PeernetClient}
|
package/exports/chain.js
CHANGED
|
@@ -1463,6 +1463,10 @@ class ConnectionMonitor {
|
|
|
1463
1463
|
#reconnectDelay = 5000;
|
|
1464
1464
|
#healthCheckInterval = 10000;
|
|
1465
1465
|
#version;
|
|
1466
|
+
// event handlers to remove later
|
|
1467
|
+
#onOnline = null;
|
|
1468
|
+
#onVisibilityChange = null;
|
|
1469
|
+
#onSigcont = null;
|
|
1466
1470
|
get isMonitoring() {
|
|
1467
1471
|
return this.#isMonitoring;
|
|
1468
1472
|
}
|
|
@@ -1482,6 +1486,33 @@ class ConnectionMonitor {
|
|
|
1482
1486
|
return;
|
|
1483
1487
|
this.#isMonitoring = true;
|
|
1484
1488
|
console.log('🔄 Starting connection monitor...');
|
|
1489
|
+
// Listen for resume/network events (browser + node/electron)
|
|
1490
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
1491
|
+
this.#onOnline = () => {
|
|
1492
|
+
console.log('🌐 Network online — attempting restore');
|
|
1493
|
+
void this.#restoreNetwork();
|
|
1494
|
+
};
|
|
1495
|
+
window.addEventListener('online', this.#onOnline);
|
|
1496
|
+
this.#onVisibilityChange = () => {
|
|
1497
|
+
if (document.visibilityState === 'visible') {
|
|
1498
|
+
console.log('💡 Visibility regained — attempting restore');
|
|
1499
|
+
void this.#restoreNetwork();
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
document.addEventListener('visibilitychange', this.#onVisibilityChange);
|
|
1503
|
+
}
|
|
1504
|
+
if (typeof process !== 'undefined' && typeof process.on === 'function') {
|
|
1505
|
+
this.#onSigcont = () => {
|
|
1506
|
+
console.log('🔔 Process resumed (SIGCONT) — attempting restore');
|
|
1507
|
+
void this.#restoreNetwork();
|
|
1508
|
+
};
|
|
1509
|
+
try {
|
|
1510
|
+
process.on('SIGCONT', this.#onSigcont);
|
|
1511
|
+
}
|
|
1512
|
+
catch (e) {
|
|
1513
|
+
// ignore if not supported
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1485
1516
|
this.#checkInterval = setInterval(() => {
|
|
1486
1517
|
this.#healthCheck();
|
|
1487
1518
|
}, this.#healthCheckInterval);
|
|
@@ -1496,6 +1527,26 @@ class ConnectionMonitor {
|
|
|
1496
1527
|
clearInterval(this.#checkInterval);
|
|
1497
1528
|
this.#checkInterval = null;
|
|
1498
1529
|
}
|
|
1530
|
+
// remove listeners
|
|
1531
|
+
if (typeof window !== 'undefined') {
|
|
1532
|
+
if (this.#onOnline) {
|
|
1533
|
+
window.removeEventListener('online', this.#onOnline);
|
|
1534
|
+
this.#onOnline = null;
|
|
1535
|
+
}
|
|
1536
|
+
if (this.#onVisibilityChange) {
|
|
1537
|
+
document.removeEventListener('visibilitychange', this.#onVisibilityChange);
|
|
1538
|
+
this.#onVisibilityChange = null;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (typeof process !== 'undefined' && typeof process.removeListener === 'function' && this.#onSigcont) {
|
|
1542
|
+
try {
|
|
1543
|
+
process.removeListener('SIGCONT', this.#onSigcont);
|
|
1544
|
+
}
|
|
1545
|
+
catch (e) {
|
|
1546
|
+
// ignore
|
|
1547
|
+
}
|
|
1548
|
+
this.#onSigcont = null;
|
|
1549
|
+
}
|
|
1499
1550
|
console.log('⏹️ Connection monitor stopped');
|
|
1500
1551
|
}
|
|
1501
1552
|
async #healthCheck() {
|
|
@@ -1515,12 +1566,14 @@ class ConnectionMonitor {
|
|
|
1515
1566
|
const disconnectedPeers = this.disconnectedPeers;
|
|
1516
1567
|
if (disconnectedPeers.length > 0) {
|
|
1517
1568
|
console.warn(`⚠️ Disconnected peers: ${disconnectedPeers.map((peer) => peer.peerId).join(', ')}`);
|
|
1518
|
-
// Attempt to reconnect each disconnected peer
|
|
1519
|
-
const promises = [];
|
|
1569
|
+
// Attempt to reconnect each disconnected peer sequentially to avoid racing signaling/state
|
|
1520
1570
|
for (const peer of disconnectedPeers) {
|
|
1521
|
-
|
|
1571
|
+
// small spacing between attempts to reduce signaling races
|
|
1572
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1573
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
1574
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1575
|
+
await this.#attemptPeerReconnection(peer);
|
|
1522
1576
|
}
|
|
1523
|
-
await Promise.all(promises);
|
|
1524
1577
|
}
|
|
1525
1578
|
// Publish connection status
|
|
1526
1579
|
globalThis.pubsub?.publish('connection-status', {
|
|
@@ -1530,54 +1583,169 @@ class ConnectionMonitor {
|
|
|
1530
1583
|
});
|
|
1531
1584
|
}
|
|
1532
1585
|
async #attemptPeerReconnection(peer) {
|
|
1533
|
-
if (
|
|
1534
|
-
|
|
1535
|
-
|
|
1586
|
+
if (!peer)
|
|
1587
|
+
return;
|
|
1588
|
+
const peerId = peer.peerId || peer.id;
|
|
1589
|
+
if (!peerId)
|
|
1590
|
+
return;
|
|
1591
|
+
if (!this.#peerReconnectAttempts[peerId]) {
|
|
1592
|
+
this.#peerReconnectAttempts[peerId] = 0;
|
|
1536
1593
|
}
|
|
1537
|
-
if (
|
|
1538
|
-
|
|
1594
|
+
if (this.#peerReconnectAttempts[peerId] >= this.#maxReconnectAttempts) {
|
|
1595
|
+
console.error('❌ Max reconnection attempts reached for', peerId);
|
|
1596
|
+
this.#peerReconnectAttempts[peerId] = 0;
|
|
1597
|
+
return;
|
|
1539
1598
|
}
|
|
1540
|
-
this.#peerReconnectAttempts[
|
|
1541
|
-
console.log(`🔄 Attempting reconnection ${this.#peerReconnectAttempts[
|
|
1599
|
+
this.#peerReconnectAttempts[peerId]++;
|
|
1600
|
+
console.log(`🔄 Attempting reconnection ${this.#peerReconnectAttempts[peerId]}/${this.#maxReconnectAttempts} for ${peerId}`);
|
|
1542
1601
|
try {
|
|
1543
|
-
const
|
|
1544
|
-
|
|
1545
|
-
|
|
1602
|
+
const peernet = globalThis.peernet;
|
|
1603
|
+
if (!peernet) {
|
|
1604
|
+
console.warn('⚠️ globalThis.peernet not available');
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
// Try targeted reconnect if available
|
|
1608
|
+
if (peernet.client?.reconnect) {
|
|
1609
|
+
try {
|
|
1610
|
+
await peernet.client.reconnect(peerId, peernet.stars?.[0]);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
catch (err) {
|
|
1614
|
+
const msg = String(err?.message || err);
|
|
1615
|
+
console.warn('⚠️ Targeted reconnect failed:', msg);
|
|
1616
|
+
// handle signaling/state mismatches by cleaning up only that peer and retrying targeted reconnect
|
|
1617
|
+
if (msg.includes('Called in wrong state') ||
|
|
1618
|
+
msg.includes('setRemoteDescription') ||
|
|
1619
|
+
msg.includes('channelNames') ||
|
|
1620
|
+
msg.includes("channelNames don't match")) {
|
|
1621
|
+
console.warn('⚠️ Detected signaling/channel mismatch — cleaning up peer state and retrying targeted reconnect');
|
|
1622
|
+
try {
|
|
1623
|
+
await this.#cleanupPeerState(peerId, peernet);
|
|
1624
|
+
// small backoff before retry
|
|
1625
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
1626
|
+
await peernet.client.reconnect(peerId, peernet.stars?.[0]);
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
catch (retryErr) {
|
|
1630
|
+
console.warn('⚠️ Retry targeted reconnect failed:', String(retryErr?.message || retryErr));
|
|
1631
|
+
// fall through to non-targeted fallback below
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
throw err;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
// If no targeted reconnect, try start/restore
|
|
1638
|
+
if (peernet.start) {
|
|
1639
|
+
await peernet.start();
|
|
1640
|
+
}
|
|
1546
1641
|
}
|
|
1547
1642
|
catch (error) {
|
|
1548
|
-
console.error('❌ Reconnection failed:', error
|
|
1643
|
+
console.error('❌ Reconnection failed:', error?.message || error);
|
|
1644
|
+
// As fallback, try full restart only if even the cleanup+retry failed
|
|
1645
|
+
if (globalThis.peernet) {
|
|
1646
|
+
await this.#performFullRestart(globalThis.peernet);
|
|
1647
|
+
}
|
|
1549
1648
|
}
|
|
1550
|
-
// // Try to restart the network
|
|
1551
|
-
// if (globalThis.peernet?.start) {
|
|
1552
|
-
// await globalThis.peernet.start()
|
|
1553
|
-
// } else {
|
|
1554
|
-
// console.warn('⚠️ Peernet start method not available, skipping reconnection')
|
|
1555
|
-
// }
|
|
1556
|
-
// } catch (error) {
|
|
1557
|
-
// console.error('❌ Reconnection failed:', error.message)
|
|
1558
|
-
// }
|
|
1559
1649
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1650
|
+
// helper: try to close/destroy a single peer connection and remove it from peernet's map
|
|
1651
|
+
async #cleanupPeerState(peerId, peernet) {
|
|
1562
1652
|
try {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
//
|
|
1568
|
-
|
|
1653
|
+
const conns = peernet.connections || {};
|
|
1654
|
+
const conn = conns[peerId] || conns[Object.keys(conns).find((k) => k.includes(peerId) || peerId.includes(k))];
|
|
1655
|
+
if (!conn)
|
|
1656
|
+
return;
|
|
1657
|
+
// close underlying RTCPeerConnection if exposed
|
|
1658
|
+
try {
|
|
1659
|
+
if (conn.pc && typeof conn.pc.close === 'function') {
|
|
1660
|
+
conn.pc.close();
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
catch (e) {
|
|
1664
|
+
// ignore
|
|
1665
|
+
}
|
|
1666
|
+
// call any destroy/cleanup API on the connection object
|
|
1667
|
+
try {
|
|
1668
|
+
if (typeof conn.destroy === 'function') {
|
|
1669
|
+
conn.destroy();
|
|
1670
|
+
}
|
|
1671
|
+
else if (typeof conn.close === 'function') {
|
|
1672
|
+
conn.close();
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
catch (e) {
|
|
1676
|
+
// ignore
|
|
1677
|
+
}
|
|
1678
|
+
// remove reference so reconnect path will create a fresh one
|
|
1679
|
+
try {
|
|
1680
|
+
delete peernet.connections[peerId];
|
|
1681
|
+
}
|
|
1682
|
+
catch (e) {
|
|
1683
|
+
// ignore
|
|
1684
|
+
}
|
|
1685
|
+
// small pause to let underlying sockets/RTCs settle
|
|
1686
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1569
1687
|
}
|
|
1570
|
-
catch (
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1688
|
+
catch (e) {
|
|
1689
|
+
// ignore cleanup errors
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
// New helper: close stale RTCPeerConnections if present and then restart peernet
|
|
1693
|
+
async #performFullRestart(peernet) {
|
|
1694
|
+
try {
|
|
1695
|
+
// Close underlying peer RTCPeerConnections if the library exposes them
|
|
1696
|
+
try {
|
|
1697
|
+
const conns = peernet.connections || {};
|
|
1698
|
+
for (const id of Object.keys(conns)) {
|
|
1699
|
+
const p = conns[id];
|
|
1700
|
+
// try to close underlying RTCPeerConnection if exposed
|
|
1701
|
+
if (p && p.pc && typeof p.pc.close === 'function') {
|
|
1702
|
+
try {
|
|
1703
|
+
p.pc.close();
|
|
1704
|
+
}
|
|
1705
|
+
catch (e) {
|
|
1706
|
+
// ignore
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
catch (e) {
|
|
1712
|
+
// ignore
|
|
1713
|
+
}
|
|
1714
|
+
// If the library supports stop -> start, do that to fully reset signaling state
|
|
1715
|
+
if (typeof peernet.stop === 'function') {
|
|
1716
|
+
try {
|
|
1717
|
+
await peernet.stop();
|
|
1718
|
+
}
|
|
1719
|
+
catch (e) {
|
|
1720
|
+
// ignore stop errors
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
// small delay to ensure sockets/RTCs are closed
|
|
1724
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
1725
|
+
if (typeof peernet.start === 'function') {
|
|
1726
|
+
await peernet.start();
|
|
1575
1727
|
}
|
|
1576
1728
|
else {
|
|
1577
|
-
|
|
1578
|
-
this.#reconnectDelay = Math.min(this.#reconnectDelay * 1.5, 30000);
|
|
1579
|
-
console.warn(`⚠️ Increasing reconnection delay to ${this.#reconnectDelay} ms`);
|
|
1729
|
+
console.warn('⚠️ peernet.start not available for full restart');
|
|
1580
1730
|
}
|
|
1731
|
+
// reset reconnect attempts so we can try fresh
|
|
1732
|
+
this.#peerReconnectAttempts = {};
|
|
1733
|
+
console.log('✅ Full peernet restart completed');
|
|
1734
|
+
}
|
|
1735
|
+
catch (e) {
|
|
1736
|
+
console.error('❌ Full restart failed:', e?.message || e);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
// Called on visibility/online/resume events
|
|
1740
|
+
async #restoreNetwork() {
|
|
1741
|
+
console.log('🔁 Restoring network after resume/wake');
|
|
1742
|
+
// If there is a peernet instance, try a safe restore
|
|
1743
|
+
if (globalThis.peernet) {
|
|
1744
|
+
await this.#performFullRestart(globalThis.peernet);
|
|
1745
|
+
}
|
|
1746
|
+
else {
|
|
1747
|
+
// If no global peernet, attempt a normal reconnection flow
|
|
1748
|
+
await this.#attemptReconnection();
|
|
1581
1749
|
}
|
|
1582
1750
|
}
|
|
1583
1751
|
async waitForPeers(timeoutMs = 30000) {
|
|
@@ -1597,6 +1765,39 @@ class ConnectionMonitor {
|
|
|
1597
1765
|
checkPeers();
|
|
1598
1766
|
});
|
|
1599
1767
|
}
|
|
1768
|
+
// New: attempt reconnection flow (gentle start + sequential per-peer reconnect)
|
|
1769
|
+
async #attemptReconnection() {
|
|
1770
|
+
console.warn('⚠️ Attempting to reconnect to peers...');
|
|
1771
|
+
try {
|
|
1772
|
+
// gentle restore: if peernet supports start(), try that first
|
|
1773
|
+
if (globalThis.peernet?.start) {
|
|
1774
|
+
await globalThis.peernet.start();
|
|
1775
|
+
}
|
|
1776
|
+
// attempt targeted reconnection for disconnected peers sequentially (avoid racing WebRTC state)
|
|
1777
|
+
const disconnected = this.disconnectedPeers;
|
|
1778
|
+
for (const p of disconnected) {
|
|
1779
|
+
// small spacing between attempts to reduce signaling races
|
|
1780
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1781
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
1782
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1783
|
+
await this.#attemptPeerReconnection(p);
|
|
1784
|
+
}
|
|
1785
|
+
// pause before next health check cycle
|
|
1786
|
+
await new Promise((resolve) => setTimeout(resolve, this.#reconnectDelay));
|
|
1787
|
+
}
|
|
1788
|
+
catch (error) {
|
|
1789
|
+
console.error('❌ Reconnection failed:', error?.message || error);
|
|
1790
|
+
if (this.#reconnectDelay >= 30000) {
|
|
1791
|
+
console.warn('⚠️ Reconnection delay reached maximum, resetting to 5 seconds');
|
|
1792
|
+
this.#reconnectDelay = 5000;
|
|
1793
|
+
}
|
|
1794
|
+
else {
|
|
1795
|
+
// exponential-ish backoff
|
|
1796
|
+
this.#reconnectDelay = Math.min(this.#reconnectDelay * 1.5, 30000);
|
|
1797
|
+
console.warn(`⚠️ Increasing reconnection delay to ${this.#reconnectDelay} ms`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1600
1801
|
}
|
|
1601
1802
|
|
|
1602
1803
|
const debug = globalThis.createDebugger('leofcoin/chain');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leofcoin/chain",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.100",
|
|
4
4
|
"description": "Official javascript implementation",
|
|
5
5
|
"private": false,
|
|
6
6
|
"exports": {
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@leofcoin/messages": "^1.4.40",
|
|
70
70
|
"@leofcoin/multi-wallet": "^3.1.8",
|
|
71
71
|
"@leofcoin/networks": "^1.1.25",
|
|
72
|
-
"@leofcoin/peernet": "^1.1.
|
|
72
|
+
"@leofcoin/peernet": "^1.1.98",
|
|
73
73
|
"@leofcoin/storage": "^3.5.38",
|
|
74
74
|
"@leofcoin/utils": "^1.1.40",
|
|
75
75
|
"@leofcoin/workers": "^1.5.27",
|