@leofcoin/chain 1.10.13 → 1.10.15
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/beacon-envelope.js +2 -2
- package/exports/beacon-epoch.js +1 -1
- package/exports/beacon-lifecycle.js +2 -2
- package/exports/beacon-round.js +1 -1
- package/exports/beacon.js +1 -1
- package/exports/browser/beacon-envelope.js +79 -3
- package/exports/browser/beacon-epoch.js +2 -1
- package/exports/browser/beacon-lifecycle.js +3 -2
- package/exports/browser/beacon-round.js +2 -1
- package/exports/browser/beacon-wire.js +3 -3
- package/exports/browser/beacon.js +3 -2
- package/exports/browser/{browser-DvU1xNFS-sdlKNCJc.js → browser-DvU1xNFS-DmmNUdE-.js} +4 -4
- package/exports/browser/{proposal.proto-BcUgd885.js → browser-crypto-B9N9KlL4.js} +35 -6
- package/exports/browser/chain.js +16 -295
- package/exports/browser/{client-B-jyclOB-CsNYfj4Z.js → client-DDC8C510-3uaB70KL.js} +36 -12
- package/exports/browser/connection-monitor-GPztgSSJ.js +454 -0
- package/exports/browser/hkdf-CnZvwpZ9.js +222 -0
- package/exports/browser/{index-Bxr5Iztg-C6xBk0rK.js → index-Bxr5Iztg-WbFIosyf.js} +4 -4
- package/exports/browser/{index-CFBGfpG1.js → index-Ccn5sExe.js} +1 -2
- package/exports/browser/{messages-FMRAS8QX-CvlZBzy5.js → messages-DH0tgNM9-C2CtyLsf.js} +4 -4
- package/exports/browser/{node-browser-xlqSBOaN.js → node-browser-BYi7D_oa.js} +6 -6
- package/exports/browser/node-browser.js +4 -4
- package/exports/browser/sha2-piimb6xL.js +614 -0
- package/exports/browser/{weierstrass-C-jX_jly.js → weierstrass-Q0QND1ZT.js} +1 -1
- package/exports/browser/workers/machine-worker.js +412 -135
- package/exports/chain.js +13 -291
- package/exports/connection-monitor.js +324 -0
- package/exports/node.js +7 -0
- package/exports/workers/machine-worker.js +412 -135
- package/package.json +8 -4
- package/exports/browser/browser-CWeoyGUw-BabtHowB.js +0 -57
- package/exports/browser/constants-Cv0p224A.js +0 -130
- package/exports/browser/hkdf-DhdhLAAv.js +0 -147
package/exports/chain.js
CHANGED
|
@@ -11,6 +11,7 @@ import { randombytes } from '@leofcoin/crypto';
|
|
|
11
11
|
import EasyWorker from '@vandeurenglenn/easy-worker';
|
|
12
12
|
import { ContractDeploymentError, ExecutionError, isResolveError, ResolveError, isExecutionError } from '@leofcoin/errors';
|
|
13
13
|
import { P as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO } from './constants-CMYKv-Rt.js';
|
|
14
|
+
import ConnectionMonitor from './connection-monitor.js';
|
|
14
15
|
import { q as quorumThreshold } from './quorum-S_qdgiAY.js';
|
|
15
16
|
import '@leofcoin/networks';
|
|
16
17
|
|
|
@@ -1650,6 +1651,12 @@ class State extends Contract {
|
|
|
1650
1651
|
const localIndex = localBlock ? Number(localBlock.index) : -1;
|
|
1651
1652
|
const remoteIndex = Number(lastBlock.index);
|
|
1652
1653
|
const remoteBlockHash = lastBlock.hash;
|
|
1654
|
+
globalThis.pubsub.publish("sync-progress", {
|
|
1655
|
+
phase: "syncing",
|
|
1656
|
+
startIndex: localIndex,
|
|
1657
|
+
localIndex,
|
|
1658
|
+
targetIndex: remoteIndex
|
|
1659
|
+
});
|
|
1653
1660
|
let localStateHash = "0x0";
|
|
1654
1661
|
try {
|
|
1655
1662
|
localStateHash = new TextDecoder().decode(await globalThis.chainStore.get("lastBlock"));
|
|
@@ -1709,6 +1716,12 @@ class State extends Contract {
|
|
|
1709
1716
|
} else {
|
|
1710
1717
|
debug$1(`Block already in local state. Remote hash: ${remoteBlockHash} matches local state`);
|
|
1711
1718
|
}
|
|
1719
|
+
globalThis.pubsub.publish("sync-progress", {
|
|
1720
|
+
phase: "synced",
|
|
1721
|
+
startIndex: localIndex,
|
|
1722
|
+
localIndex: remoteIndex,
|
|
1723
|
+
targetIndex: remoteIndex
|
|
1724
|
+
});
|
|
1712
1725
|
} catch (error) {
|
|
1713
1726
|
console.log(error);
|
|
1714
1727
|
throw error;
|
|
@@ -1995,297 +2008,6 @@ class VersionControl extends State {
|
|
|
1995
2008
|
}
|
|
1996
2009
|
}
|
|
1997
2010
|
|
|
1998
|
-
class ConnectionMonitor {
|
|
1999
|
-
#isMonitoring = false;
|
|
2000
|
-
#checkInterval = null;
|
|
2001
|
-
#reconnectDelay = 5e3;
|
|
2002
|
-
#reconnectTimer = null;
|
|
2003
|
-
#healthCheckInterval = 6e4;
|
|
2004
|
-
#version;
|
|
2005
|
-
#lastHealthCheckAt = 0;
|
|
2006
|
-
#reconnecting = false;
|
|
2007
|
-
// event handlers to remove later
|
|
2008
|
-
#onOnline = null;
|
|
2009
|
-
#onVisibilityChange = null;
|
|
2010
|
-
#onSigcont = null;
|
|
2011
|
-
#onPeerConnected = null;
|
|
2012
|
-
get isMonitoring() {
|
|
2013
|
-
return this.#isMonitoring;
|
|
2014
|
-
}
|
|
2015
|
-
get connectedPeers() {
|
|
2016
|
-
return Object.values(globalThis.peernet?.connections || {}).filter((peer) => peer.connected);
|
|
2017
|
-
}
|
|
2018
|
-
get compatiblePeers() {
|
|
2019
|
-
return this.connectedPeers.filter((peer) => {
|
|
2020
|
-
if (!peer.version || !this.#version) return false;
|
|
2021
|
-
const [peerMajor, peerMinor] = peer.version.split(".");
|
|
2022
|
-
const [localMajor, localMinor] = this.#version.split(".");
|
|
2023
|
-
return peerMajor === localMajor && peerMinor === localMinor;
|
|
2024
|
-
});
|
|
2025
|
-
}
|
|
2026
|
-
get disconnectedPeers() {
|
|
2027
|
-
return Object.values(globalThis.peernet?.connections || {}).filter((peer) => !peer.connected);
|
|
2028
|
-
}
|
|
2029
|
-
start(version) {
|
|
2030
|
-
this.#version = version;
|
|
2031
|
-
console.log(`\u{1F517} Connection Monitor initialized for version: ${this.#version}`);
|
|
2032
|
-
if (this.#isMonitoring) return;
|
|
2033
|
-
this.#isMonitoring = true;
|
|
2034
|
-
console.log("\u{1F504} Starting connection monitor...");
|
|
2035
|
-
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
2036
|
-
this.#onOnline = () => {
|
|
2037
|
-
console.log("\u{1F310} Network online \u2014 attempting restore");
|
|
2038
|
-
void this.#restoreNetwork();
|
|
2039
|
-
};
|
|
2040
|
-
window.addEventListener("online", this.#onOnline);
|
|
2041
|
-
this.#onVisibilityChange = () => {
|
|
2042
|
-
if (document.visibilityState === "visible") {
|
|
2043
|
-
console.log("\u{1F4A1} Visibility regained");
|
|
2044
|
-
if (this.connectedPeers.length === 0) {
|
|
2045
|
-
console.log("\u{1F4A1} Visibility regained \u2014 attempting restore");
|
|
2046
|
-
void this.#restoreNetwork();
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
};
|
|
2050
|
-
document.addEventListener("visibilitychange", this.#onVisibilityChange);
|
|
2051
|
-
}
|
|
2052
|
-
if (typeof process !== "undefined" && typeof process.on === "function") {
|
|
2053
|
-
this.#onSigcont = () => {
|
|
2054
|
-
console.log("\u{1F514} Process resumed (SIGCONT) \u2014 attempting restore");
|
|
2055
|
-
void this.#restoreNetwork();
|
|
2056
|
-
};
|
|
2057
|
-
try {
|
|
2058
|
-
process.on("SIGCONT", this.#onSigcont);
|
|
2059
|
-
} catch (e) {
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
this.#lastHealthCheckAt = Date.now();
|
|
2063
|
-
this.#checkInterval = setInterval(() => {
|
|
2064
|
-
this.#healthCheck();
|
|
2065
|
-
}, this.#healthCheckInterval);
|
|
2066
|
-
this.#onPeerConnected = () => {
|
|
2067
|
-
if (this.#reconnectTimer) {
|
|
2068
|
-
clearTimeout(this.#reconnectTimer);
|
|
2069
|
-
this.#reconnectTimer = null;
|
|
2070
|
-
}
|
|
2071
|
-
this.#reconnecting = false;
|
|
2072
|
-
this.#reconnectDelay = 5e3;
|
|
2073
|
-
};
|
|
2074
|
-
globalThis.pubsub?.subscribe("peer:connected", this.#onPeerConnected);
|
|
2075
|
-
}
|
|
2076
|
-
stop() {
|
|
2077
|
-
if (!this.#isMonitoring) return;
|
|
2078
|
-
this.#isMonitoring = false;
|
|
2079
|
-
if (this.#checkInterval) {
|
|
2080
|
-
clearInterval(this.#checkInterval);
|
|
2081
|
-
this.#checkInterval = null;
|
|
2082
|
-
}
|
|
2083
|
-
if (typeof window !== "undefined") {
|
|
2084
|
-
if (this.#onOnline) {
|
|
2085
|
-
window.removeEventListener("online", this.#onOnline);
|
|
2086
|
-
this.#onOnline = null;
|
|
2087
|
-
}
|
|
2088
|
-
if (this.#onVisibilityChange) {
|
|
2089
|
-
document.removeEventListener("visibilitychange", this.#onVisibilityChange);
|
|
2090
|
-
this.#onVisibilityChange = null;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
if (typeof process !== "undefined" && typeof process.removeListener === "function" && this.#onSigcont) {
|
|
2094
|
-
try {
|
|
2095
|
-
process.removeListener("SIGCONT", this.#onSigcont);
|
|
2096
|
-
} catch (e) {
|
|
2097
|
-
}
|
|
2098
|
-
this.#onSigcont = null;
|
|
2099
|
-
}
|
|
2100
|
-
if (this.#onPeerConnected) {
|
|
2101
|
-
globalThis.pubsub?.unsubscribe("peer:connected", this.#onPeerConnected);
|
|
2102
|
-
this.#onPeerConnected = null;
|
|
2103
|
-
}
|
|
2104
|
-
if (this.#reconnectTimer) {
|
|
2105
|
-
clearTimeout(this.#reconnectTimer);
|
|
2106
|
-
this.#reconnectTimer = null;
|
|
2107
|
-
}
|
|
2108
|
-
console.log("\u23F9\uFE0F Connection monitor stopped");
|
|
2109
|
-
}
|
|
2110
|
-
async #healthCheck() {
|
|
2111
|
-
const now = Date.now();
|
|
2112
|
-
const expectedNext = this.#lastHealthCheckAt + this.#healthCheckInterval;
|
|
2113
|
-
const drift = now - expectedNext;
|
|
2114
|
-
this.#lastHealthCheckAt = now;
|
|
2115
|
-
if (drift > 5e3) {
|
|
2116
|
-
console.log(`\u23F0 Detected timer drift of ${drift}ms (likely system sleep) \u2014 attempting restore`);
|
|
2117
|
-
void this.#restoreNetwork();
|
|
2118
|
-
}
|
|
2119
|
-
const connectedPeers = this.connectedPeers;
|
|
2120
|
-
const compatiblePeers = this.compatiblePeers;
|
|
2121
|
-
const disconnectedPeers = this.disconnectedPeers;
|
|
2122
|
-
console.log(
|
|
2123
|
-
`\u{1F50D} Health check: ${connectedPeers.length} connected, ${compatiblePeers.length} compatible, ${disconnectedPeers.length} negotiating`
|
|
2124
|
-
);
|
|
2125
|
-
if (connectedPeers.length === 0 && disconnectedPeers.length === 0) {
|
|
2126
|
-
console.warn("\u26A0\uFE0F No peer connections detected \u2014 attempting reconnection");
|
|
2127
|
-
await this.#attemptReconnection();
|
|
2128
|
-
} else if (compatiblePeers.length === 0 && connectedPeers.length > 0) {
|
|
2129
|
-
console.warn("\u26A0\uFE0F No compatible peers found \u2014 attempting reconnection");
|
|
2130
|
-
await this.#attemptReconnection();
|
|
2131
|
-
}
|
|
2132
|
-
globalThis.pubsub?.publish("connection-status", {
|
|
2133
|
-
connected: connectedPeers.length,
|
|
2134
|
-
compatible: compatiblePeers.length,
|
|
2135
|
-
healthy: compatiblePeers.length > 0
|
|
2136
|
-
});
|
|
2137
|
-
}
|
|
2138
|
-
// lightweight TCP probe to detect internet connectivity in Node.js
|
|
2139
|
-
async #isOnLine(timeout = 1500) {
|
|
2140
|
-
if (navigator?.onLine !== void 0) {
|
|
2141
|
-
return navigator.onLine;
|
|
2142
|
-
}
|
|
2143
|
-
return new Promise(async (resolve) => {
|
|
2144
|
-
try {
|
|
2145
|
-
const net = await import('net');
|
|
2146
|
-
const socket = new net.Socket();
|
|
2147
|
-
let finished = false;
|
|
2148
|
-
const finish = (val) => {
|
|
2149
|
-
if (finished) return;
|
|
2150
|
-
finished = true;
|
|
2151
|
-
try {
|
|
2152
|
-
socket.destroy();
|
|
2153
|
-
} catch (e) {
|
|
2154
|
-
}
|
|
2155
|
-
resolve(val);
|
|
2156
|
-
};
|
|
2157
|
-
socket.setTimeout(timeout);
|
|
2158
|
-
socket.once("connect", () => finish(true));
|
|
2159
|
-
socket.once("error", () => finish(false));
|
|
2160
|
-
socket.once("timeout", () => finish(false));
|
|
2161
|
-
socket.connect(53, "1.1.1.1");
|
|
2162
|
-
} catch (e) {
|
|
2163
|
-
resolve(false);
|
|
2164
|
-
}
|
|
2165
|
-
});
|
|
2166
|
-
}
|
|
2167
|
-
// Called on visibility/online/resume events
|
|
2168
|
-
async #restoreNetwork() {
|
|
2169
|
-
if (this.#reconnecting) {
|
|
2170
|
-
console.log("\u{1F501} Reconnection already in progress, skipping");
|
|
2171
|
-
return;
|
|
2172
|
-
}
|
|
2173
|
-
this.#reconnecting = true;
|
|
2174
|
-
if (this.connectedPeers.length > 0) {
|
|
2175
|
-
console.log("\u2705 Already connected to peers, skipping restoration");
|
|
2176
|
-
this.#reconnecting = false;
|
|
2177
|
-
return;
|
|
2178
|
-
}
|
|
2179
|
-
console.log("\u{1F501} Restoring network");
|
|
2180
|
-
try {
|
|
2181
|
-
const online = await this.#isOnLine(1500);
|
|
2182
|
-
if (!online) {
|
|
2183
|
-
console.warn("\u26A0\uFE0F No internet detected, skipping restore");
|
|
2184
|
-
this.#reconnecting = false;
|
|
2185
|
-
return;
|
|
2186
|
-
}
|
|
2187
|
-
} catch (e) {
|
|
2188
|
-
console.warn("\u26A0\uFE0F Online probe failed, proceeding with restore", e?.message || e);
|
|
2189
|
-
}
|
|
2190
|
-
try {
|
|
2191
|
-
console.log("\u{1F504} Attempting network restoration...");
|
|
2192
|
-
if (globalThis.peernet?.client?.reinit) {
|
|
2193
|
-
console.log(" \u2192 Trying client.reinit()");
|
|
2194
|
-
try {
|
|
2195
|
-
await globalThis.peernet.client.reinit();
|
|
2196
|
-
console.log(" \u2705 client.reinit() succeeded");
|
|
2197
|
-
} catch (e) {
|
|
2198
|
-
console.warn(" \u26A0\uFE0F client.reinit() failed:", e?.message || e);
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
if (globalThis.peernet?.start) {
|
|
2202
|
-
console.log(" \u2192 Trying peernet.start()");
|
|
2203
|
-
try {
|
|
2204
|
-
await globalThis.peernet.start();
|
|
2205
|
-
console.log(" \u2705 peernet.start() succeeded");
|
|
2206
|
-
} catch (e) {
|
|
2207
|
-
console.warn(" \u26A0\uFE0F peernet.start() failed:", e?.message || e);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
try {
|
|
2211
|
-
const networkName = globalThis.peernet?.network;
|
|
2212
|
-
if (networkName && typeof networkName === "string") {
|
|
2213
|
-
const { default: networks } = await import('@leofcoin/networks');
|
|
2214
|
-
const [mainKey, subKey] = networkName.split(":");
|
|
2215
|
-
const networkConfig = networks?.[mainKey]?.[subKey];
|
|
2216
|
-
if (networkConfig?.stars && Array.isArray(networkConfig.stars)) {
|
|
2217
|
-
console.log(" \u2192 Attempting to dial star servers:", networkConfig.stars.join(", "));
|
|
2218
|
-
for (const star of networkConfig.stars) {
|
|
2219
|
-
try {
|
|
2220
|
-
if (globalThis.peernet?.client && "dial" in globalThis.peernet.client) {
|
|
2221
|
-
await globalThis.peernet.client.dial(star);
|
|
2222
|
-
console.log(` \u2705 Connected to star server: ${star}`);
|
|
2223
|
-
}
|
|
2224
|
-
} catch (e) {
|
|
2225
|
-
console.warn(` \u26A0\uFE0F Failed to dial ${star}:`, e?.message || e);
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
} catch (e) {
|
|
2231
|
-
console.warn(" \u26A0\uFE0F Could not load or dial star servers:", e?.message || e);
|
|
2232
|
-
}
|
|
2233
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
2234
|
-
const connectedAfter = this.connectedPeers.length;
|
|
2235
|
-
console.log(`\u{1F504} Restoration complete. Connected peers: ${connectedAfter}`);
|
|
2236
|
-
} catch (error) {
|
|
2237
|
-
console.error("\u274C Network restoration failed:", error?.message || error);
|
|
2238
|
-
} finally {
|
|
2239
|
-
this.#reconnecting = false;
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
async waitForPeers(timeoutMs = 3e4) {
|
|
2243
|
-
return new Promise((resolve) => {
|
|
2244
|
-
const startTime = Date.now();
|
|
2245
|
-
const checkPeers = () => {
|
|
2246
|
-
if (this.compatiblePeers.length > 0) {
|
|
2247
|
-
resolve(true);
|
|
2248
|
-
return;
|
|
2249
|
-
}
|
|
2250
|
-
if (Date.now() - startTime >= timeoutMs) {
|
|
2251
|
-
resolve(false);
|
|
2252
|
-
return;
|
|
2253
|
-
}
|
|
2254
|
-
setTimeout(checkPeers, 1e3);
|
|
2255
|
-
};
|
|
2256
|
-
checkPeers();
|
|
2257
|
-
});
|
|
2258
|
-
}
|
|
2259
|
-
async #attemptReconnection() {
|
|
2260
|
-
try {
|
|
2261
|
-
await this.#restoreNetwork();
|
|
2262
|
-
const hasConnections = this.connectedPeers.length > 0 || this.disconnectedPeers.length > 0;
|
|
2263
|
-
if (hasConnections) {
|
|
2264
|
-
console.log("\u2705 Reconnection successful, resetting backoff delay");
|
|
2265
|
-
this.#reconnectDelay = 5e3;
|
|
2266
|
-
} else {
|
|
2267
|
-
console.warn("\u26A0\uFE0F Reconnection attempt completed but no peers connected");
|
|
2268
|
-
if (this.#reconnectDelay >= 3e4) {
|
|
2269
|
-
console.warn("\u26A0\uFE0F Reconnection delay reached maximum, resetting to 5 seconds");
|
|
2270
|
-
this.#reconnectDelay = 5e3;
|
|
2271
|
-
} else {
|
|
2272
|
-
this.#reconnectDelay = Math.min(this.#reconnectDelay * 1.5, 3e4);
|
|
2273
|
-
console.warn(`\u26A0\uFE0F Increasing reconnection delay to ${this.#reconnectDelay} ms`);
|
|
2274
|
-
}
|
|
2275
|
-
this.#reconnectTimer = setTimeout(() => this.#attemptReconnection(), this.#reconnectDelay);
|
|
2276
|
-
}
|
|
2277
|
-
} catch (error) {
|
|
2278
|
-
console.error("\u274C Reconnection failed:", error?.message || error);
|
|
2279
|
-
if (this.#reconnectDelay >= 3e4) {
|
|
2280
|
-
this.#reconnectDelay = 5e3;
|
|
2281
|
-
} else {
|
|
2282
|
-
this.#reconnectDelay = Math.min(this.#reconnectDelay * 1.5, 3e4);
|
|
2283
|
-
}
|
|
2284
|
-
this.#reconnectTimer = setTimeout(() => this.#attemptReconnection(), this.#reconnectDelay);
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
2011
|
const validateChainLink = (localTip, incoming) => {
|
|
2290
2012
|
const localHash = localTip?.hash || "0x0";
|
|
2291
2013
|
const localIndex = Number(localTip?.index ?? -1);
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
const INITIAL_RECONNECT_DELAY = 5e3;
|
|
2
|
+
const MAX_RECONNECT_DELAY = 3e4;
|
|
3
|
+
const isSendablePeer = (peer) => {
|
|
4
|
+
if (!peer || typeof peer !== "object") return false;
|
|
5
|
+
const candidate = peer;
|
|
6
|
+
if (typeof candidate.send !== "function") return false;
|
|
7
|
+
if (candidate.connected === true) return true;
|
|
8
|
+
return typeof candidate.once === "function" && typeof candidate.removeListener === "function";
|
|
9
|
+
};
|
|
10
|
+
const guardPeerMessages = (peernet) => {
|
|
11
|
+
const sendMessage = peernet.sendMessage.bind(peernet);
|
|
12
|
+
peernet.sendMessage = (peer, ...args) => {
|
|
13
|
+
if (isSendablePeer(peer)) return sendMessage(peer, ...args);
|
|
14
|
+
for (const [peerId, connection] of Object.entries(peernet.connections || {})) {
|
|
15
|
+
if (connection === peer) delete peernet.connections[peerId];
|
|
16
|
+
}
|
|
17
|
+
return Promise.resolve(void 0);
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
class ConnectionMonitor {
|
|
21
|
+
#isMonitoring = false;
|
|
22
|
+
#checkInterval = null;
|
|
23
|
+
#reconnectDelay = INITIAL_RECONNECT_DELAY;
|
|
24
|
+
#reconnectTimer = null;
|
|
25
|
+
#healthCheckInterval = 6e4;
|
|
26
|
+
#version;
|
|
27
|
+
#lastHealthCheckAt = 0;
|
|
28
|
+
#reconnecting = false;
|
|
29
|
+
// event handlers to remove later
|
|
30
|
+
#onOnline = null;
|
|
31
|
+
#onVisibilityChange = null;
|
|
32
|
+
#onSigcont = null;
|
|
33
|
+
#onPeerConnected = null;
|
|
34
|
+
get isMonitoring() {
|
|
35
|
+
return this.#isMonitoring;
|
|
36
|
+
}
|
|
37
|
+
get connectedPeers() {
|
|
38
|
+
return Object.values(globalThis.peernet?.connections || {}).filter((peer) => peer.connected);
|
|
39
|
+
}
|
|
40
|
+
get compatiblePeers() {
|
|
41
|
+
return this.connectedPeers.filter((peer) => {
|
|
42
|
+
if (!peer.version || !this.#version) return false;
|
|
43
|
+
const [peerMajor, peerMinor] = peer.version.split(".");
|
|
44
|
+
const [localMajor, localMinor] = this.#version.split(".");
|
|
45
|
+
return peerMajor === localMajor && peerMinor === localMinor;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
get disconnectedPeers() {
|
|
49
|
+
return Object.values(globalThis.peernet?.connections || {}).filter((peer) => !peer.connected);
|
|
50
|
+
}
|
|
51
|
+
#pruneInvalidConnections() {
|
|
52
|
+
const connections = globalThis.peernet?.connections;
|
|
53
|
+
if (!connections) return;
|
|
54
|
+
for (const [peerId, peer] of Object.entries(connections)) {
|
|
55
|
+
if (!isSendablePeer(peer)) {
|
|
56
|
+
delete connections[peerId];
|
|
57
|
+
console.warn(`Removed invalid peer connection: ${peerId}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
start(version) {
|
|
62
|
+
this.#version = version;
|
|
63
|
+
console.log(`\u{1F517} Connection Monitor initialized for version: ${this.#version}`);
|
|
64
|
+
if (this.#isMonitoring) return;
|
|
65
|
+
this.#isMonitoring = true;
|
|
66
|
+
console.log("\u{1F504} Starting connection monitor...");
|
|
67
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
68
|
+
this.#onOnline = () => {
|
|
69
|
+
console.log("\u{1F310} Network online \u2014 attempting restore");
|
|
70
|
+
void this.#restoreNetwork();
|
|
71
|
+
};
|
|
72
|
+
window.addEventListener("online", this.#onOnline);
|
|
73
|
+
this.#onVisibilityChange = () => {
|
|
74
|
+
if (document.visibilityState === "visible") {
|
|
75
|
+
console.log("\u{1F4A1} Visibility regained");
|
|
76
|
+
if (this.connectedPeers.length === 0) {
|
|
77
|
+
console.log("\u{1F4A1} Visibility regained \u2014 attempting restore");
|
|
78
|
+
void this.#restoreNetwork();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
document.addEventListener("visibilitychange", this.#onVisibilityChange);
|
|
83
|
+
}
|
|
84
|
+
if (typeof process !== "undefined" && typeof process.on === "function") {
|
|
85
|
+
this.#onSigcont = () => {
|
|
86
|
+
console.log("\u{1F514} Process resumed (SIGCONT) \u2014 attempting restore");
|
|
87
|
+
void this.#restoreNetwork();
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
process.on("SIGCONT", this.#onSigcont);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this.#lastHealthCheckAt = Date.now();
|
|
95
|
+
this.#checkInterval = setInterval(() => {
|
|
96
|
+
this.#healthCheck();
|
|
97
|
+
}, this.#healthCheckInterval);
|
|
98
|
+
this.#onPeerConnected = () => {
|
|
99
|
+
if (this.#reconnectTimer) {
|
|
100
|
+
clearTimeout(this.#reconnectTimer);
|
|
101
|
+
this.#reconnectTimer = null;
|
|
102
|
+
}
|
|
103
|
+
this.#reconnecting = false;
|
|
104
|
+
this.#reconnectDelay = INITIAL_RECONNECT_DELAY;
|
|
105
|
+
};
|
|
106
|
+
globalThis.pubsub?.subscribe("peer:connected", this.#onPeerConnected);
|
|
107
|
+
}
|
|
108
|
+
stop() {
|
|
109
|
+
if (!this.#isMonitoring) return;
|
|
110
|
+
this.#isMonitoring = false;
|
|
111
|
+
if (this.#checkInterval) {
|
|
112
|
+
clearInterval(this.#checkInterval);
|
|
113
|
+
this.#checkInterval = null;
|
|
114
|
+
}
|
|
115
|
+
if (typeof window !== "undefined") {
|
|
116
|
+
if (this.#onOnline) {
|
|
117
|
+
window.removeEventListener("online", this.#onOnline);
|
|
118
|
+
this.#onOnline = null;
|
|
119
|
+
}
|
|
120
|
+
if (this.#onVisibilityChange) {
|
|
121
|
+
document.removeEventListener("visibilitychange", this.#onVisibilityChange);
|
|
122
|
+
this.#onVisibilityChange = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (typeof process !== "undefined" && typeof process.removeListener === "function" && this.#onSigcont) {
|
|
126
|
+
try {
|
|
127
|
+
process.removeListener("SIGCONT", this.#onSigcont);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
}
|
|
130
|
+
this.#onSigcont = null;
|
|
131
|
+
}
|
|
132
|
+
if (this.#onPeerConnected) {
|
|
133
|
+
globalThis.pubsub?.unsubscribe("peer:connected", this.#onPeerConnected);
|
|
134
|
+
this.#onPeerConnected = null;
|
|
135
|
+
}
|
|
136
|
+
if (this.#reconnectTimer) {
|
|
137
|
+
clearTimeout(this.#reconnectTimer);
|
|
138
|
+
this.#reconnectTimer = null;
|
|
139
|
+
}
|
|
140
|
+
console.log("\u23F9\uFE0F Connection monitor stopped");
|
|
141
|
+
}
|
|
142
|
+
async #healthCheck() {
|
|
143
|
+
this.#pruneInvalidConnections();
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
const expectedNext = this.#lastHealthCheckAt + this.#healthCheckInterval;
|
|
146
|
+
const drift = now - expectedNext;
|
|
147
|
+
this.#lastHealthCheckAt = now;
|
|
148
|
+
if (drift > 5e3) {
|
|
149
|
+
console.log(`\u23F0 Detected timer drift of ${drift}ms (likely system sleep) \u2014 attempting restore`);
|
|
150
|
+
void this.#restoreNetwork();
|
|
151
|
+
}
|
|
152
|
+
const connectedPeers = this.connectedPeers;
|
|
153
|
+
const compatiblePeers = this.compatiblePeers;
|
|
154
|
+
const disconnectedPeers = this.disconnectedPeers;
|
|
155
|
+
console.log(
|
|
156
|
+
`\u{1F50D} Health check: ${connectedPeers.length} connected, ${compatiblePeers.length} compatible, ${disconnectedPeers.length} negotiating`
|
|
157
|
+
);
|
|
158
|
+
if (connectedPeers.length === 0 && disconnectedPeers.length === 0 && !this.#reconnectTimer) {
|
|
159
|
+
console.warn("\u26A0\uFE0F No peer connections detected \u2014 attempting reconnection");
|
|
160
|
+
await this.#attemptReconnection();
|
|
161
|
+
} else if (compatiblePeers.length === 0 && connectedPeers.length > 0 && !this.#reconnectTimer) {
|
|
162
|
+
console.warn("\u26A0\uFE0F No compatible peers found \u2014 attempting reconnection");
|
|
163
|
+
await this.#attemptReconnection();
|
|
164
|
+
}
|
|
165
|
+
globalThis.pubsub?.publish("connection-status", {
|
|
166
|
+
connected: connectedPeers.length,
|
|
167
|
+
compatible: compatiblePeers.length,
|
|
168
|
+
healthy: compatiblePeers.length > 0
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// lightweight TCP probe to detect internet connectivity in Node.js
|
|
172
|
+
async #isOnLine(timeout = 1500) {
|
|
173
|
+
if (navigator?.onLine !== void 0) {
|
|
174
|
+
return navigator.onLine;
|
|
175
|
+
}
|
|
176
|
+
return new Promise(async (resolve) => {
|
|
177
|
+
try {
|
|
178
|
+
const net = await import('net');
|
|
179
|
+
const socket = new net.Socket();
|
|
180
|
+
let finished = false;
|
|
181
|
+
const finish = (val) => {
|
|
182
|
+
if (finished) return;
|
|
183
|
+
finished = true;
|
|
184
|
+
try {
|
|
185
|
+
socket.destroy();
|
|
186
|
+
} catch (e) {
|
|
187
|
+
}
|
|
188
|
+
resolve(val);
|
|
189
|
+
};
|
|
190
|
+
socket.setTimeout(timeout);
|
|
191
|
+
socket.once("connect", () => finish(true));
|
|
192
|
+
socket.once("error", () => finish(false));
|
|
193
|
+
socket.once("timeout", () => finish(false));
|
|
194
|
+
socket.connect(53, "1.1.1.1");
|
|
195
|
+
} catch (e) {
|
|
196
|
+
resolve(false);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// Called on visibility/online/resume events
|
|
201
|
+
async #restoreNetwork() {
|
|
202
|
+
if (this.#reconnecting) {
|
|
203
|
+
console.log("\u{1F501} Reconnection already in progress, skipping");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.#reconnecting = true;
|
|
207
|
+
if (this.connectedPeers.length > 0) {
|
|
208
|
+
console.log("\u2705 Already connected to peers, skipping restoration");
|
|
209
|
+
this.#reconnecting = false;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
console.log("\u{1F501} Restoring network");
|
|
213
|
+
try {
|
|
214
|
+
const online = await this.#isOnLine(1500);
|
|
215
|
+
if (!online) {
|
|
216
|
+
console.warn("\u26A0\uFE0F No internet detected, skipping restore");
|
|
217
|
+
this.#reconnecting = false;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.warn("\u26A0\uFE0F Online probe failed, proceeding with restore", e?.message || e);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
console.log("\u{1F504} Attempting network restoration...");
|
|
225
|
+
if (globalThis.peernet?.client?.reinit) {
|
|
226
|
+
console.log(" \u2192 Trying client.reinit()");
|
|
227
|
+
try {
|
|
228
|
+
await globalThis.peernet.client.reinit();
|
|
229
|
+
console.log(" \u2705 client.reinit() succeeded");
|
|
230
|
+
} catch (e) {
|
|
231
|
+
console.warn(" \u26A0\uFE0F client.reinit() failed:", e?.message || e);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (globalThis.peernet?.start) {
|
|
235
|
+
console.log(" \u2192 Trying peernet.start()");
|
|
236
|
+
try {
|
|
237
|
+
await globalThis.peernet.start();
|
|
238
|
+
console.log(" \u2705 peernet.start() succeeded");
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.warn(" \u26A0\uFE0F peernet.start() failed:", e?.message || e);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const networkName = globalThis.peernet?.network;
|
|
245
|
+
if (networkName && typeof networkName === "string") {
|
|
246
|
+
const { default: networks } = await import('@leofcoin/networks');
|
|
247
|
+
const [mainKey, subKey] = networkName.split(":");
|
|
248
|
+
const networkConfig = networks?.[mainKey]?.[subKey];
|
|
249
|
+
if (networkConfig?.stars && Array.isArray(networkConfig.stars)) {
|
|
250
|
+
console.log(" \u2192 Attempting to dial star servers:", networkConfig.stars.join(", "));
|
|
251
|
+
for (const star of networkConfig.stars) {
|
|
252
|
+
try {
|
|
253
|
+
if (globalThis.peernet?.client && "dial" in globalThis.peernet.client) {
|
|
254
|
+
await globalThis.peernet.client.dial(star);
|
|
255
|
+
console.log(` \u2705 Connected to star server: ${star}`);
|
|
256
|
+
}
|
|
257
|
+
} catch (e) {
|
|
258
|
+
console.warn(` \u26A0\uFE0F Failed to dial ${star}:`, e?.message || e);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch (e) {
|
|
264
|
+
console.warn(" \u26A0\uFE0F Could not load or dial star servers:", e?.message || e);
|
|
265
|
+
}
|
|
266
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
267
|
+
this.#pruneInvalidConnections();
|
|
268
|
+
const connectedAfter = this.connectedPeers.length;
|
|
269
|
+
console.log(`\u{1F504} Restoration complete. Connected peers: ${connectedAfter}`);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error("\u274C Network restoration failed:", error?.message || error);
|
|
272
|
+
} finally {
|
|
273
|
+
this.#reconnecting = false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async waitForPeers(timeoutMs = 3e4) {
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
const startTime = Date.now();
|
|
279
|
+
const checkPeers = () => {
|
|
280
|
+
if (this.compatiblePeers.length > 0) {
|
|
281
|
+
resolve(true);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (Date.now() - startTime >= timeoutMs) {
|
|
285
|
+
resolve(false);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
setTimeout(checkPeers, 1e3);
|
|
289
|
+
};
|
|
290
|
+
checkPeers();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
async #attemptReconnection() {
|
|
294
|
+
try {
|
|
295
|
+
await this.#restoreNetwork();
|
|
296
|
+
const hasConnections = this.connectedPeers.length > 0 || this.disconnectedPeers.length > 0;
|
|
297
|
+
if (hasConnections) {
|
|
298
|
+
console.log("\u2705 Reconnection successful, resetting backoff delay");
|
|
299
|
+
this.#reconnectDelay = INITIAL_RECONNECT_DELAY;
|
|
300
|
+
} else {
|
|
301
|
+
console.warn("\u26A0\uFE0F Reconnection attempt completed but no peers connected");
|
|
302
|
+
this.#scheduleReconnect();
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error("\u274C Reconnection failed:", error?.message || error);
|
|
306
|
+
this.#scheduleReconnect();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
#scheduleReconnect() {
|
|
310
|
+
if (!this.#isMonitoring || this.#reconnectTimer) return;
|
|
311
|
+
if (this.#reconnectDelay >= MAX_RECONNECT_DELAY) {
|
|
312
|
+
console.warn("\u26A0\uFE0F No peers available; automatic retries paused until the next health check or network event");
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.#reconnectDelay = Math.min(this.#reconnectDelay * 1.5, MAX_RECONNECT_DELAY);
|
|
316
|
+
console.warn(`\u26A0\uFE0F Increasing reconnection delay to ${this.#reconnectDelay} ms`);
|
|
317
|
+
this.#reconnectTimer = setTimeout(() => {
|
|
318
|
+
this.#reconnectTimer = null;
|
|
319
|
+
void this.#attemptReconnection();
|
|
320
|
+
}, this.#reconnectDelay);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { ConnectionMonitor as default, guardPeerMessages, isSendablePeer };
|
package/exports/node.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Peernet from '@leofcoin/peernet';
|
|
2
2
|
import nodeConfig from '@leofcoin/lib/node-config';
|
|
3
3
|
import { D as DEFAULT_NODE_OPTIONS } from './constants-CMYKv-Rt.js';
|
|
4
|
+
import { guardPeerMessages } from './connection-monitor.js';
|
|
4
5
|
import '@leofcoin/networks';
|
|
5
6
|
|
|
6
7
|
class Node {
|
|
@@ -10,11 +11,17 @@ class Node {
|
|
|
10
11
|
}
|
|
11
12
|
async _init(config = { autoStart: false }, password) {
|
|
12
13
|
config = { ...DEFAULT_NODE_OPTIONS, ...config };
|
|
14
|
+
const backend = config.transport?.webrtc?.backend || process.env.NETPEER_SWARM_WRTC_BACKEND || "vandeurenglenn";
|
|
15
|
+
if (backend === "vandeurenglenn" && !globalThis.RTCPeerConnection && !globalThis.wrtc) {
|
|
16
|
+
globalThis.wrtc = await import('@vandeurenglenn/wrtc');
|
|
17
|
+
globalThis.__swarmWrtcImpl = backend;
|
|
18
|
+
}
|
|
13
19
|
if (config.storeNamespace && !config.root) {
|
|
14
20
|
const [network, networkVersion] = config.network.split(":");
|
|
15
21
|
config.root = `.${network}/${networkVersion || config.networkVersion}/${config.storeNamespace}`;
|
|
16
22
|
}
|
|
17
23
|
this.#node = globalThis.Peernet ? await new globalThis.Peernet(config, password) : await new Peernet(config, password);
|
|
24
|
+
guardPeerMessages(this.#node);
|
|
18
25
|
await nodeConfig(config);
|
|
19
26
|
globalThis.pubsub.subscribe("chain:ready", async () => {
|
|
20
27
|
if (!this.#node.autoStart) {
|