querysub 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dependency-cruiser.js +304 -0
- package/.eslintrc.js +51 -0
- package/.github/copilot-instructions.md +1 -0
- package/.vscode/settings.json +25 -0
- package/bin/deploy.js +4 -0
- package/bin/function.js +4 -0
- package/bin/server.js +4 -0
- package/costsBenefits.txt +112 -0
- package/deploy.ts +3 -0
- package/inject.ts +1 -0
- package/package.json +60 -0
- package/prompts.txt +54 -0
- package/spec.txt +820 -0
- package/src/-a-archives/archiveCache.ts +913 -0
- package/src/-a-archives/archives.ts +148 -0
- package/src/-a-archives/archivesBackBlaze.ts +792 -0
- package/src/-a-archives/archivesDisk.ts +418 -0
- package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
- package/src/-a-auth/certs.ts +517 -0
- package/src/-a-auth/der.ts +122 -0
- package/src/-a-auth/ed25519.ts +1015 -0
- package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
- package/src/-b-authorities/dnsAuthority.ts +203 -0
- package/src/-b-authorities/emailAuthority.ts +57 -0
- package/src/-c-identity/IdentityController.ts +200 -0
- package/src/-d-trust/NetworkTrust2.ts +150 -0
- package/src/-e-certs/EdgeCertController.ts +288 -0
- package/src/-e-certs/certAuthority.ts +192 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
- package/src/-g-core-values/NodeCapabilities.ts +134 -0
- package/src/-g-core-values/oneTimeForward.ts +91 -0
- package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
- package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
- package/src/0-path-value-core/LoggingClient.tsx +24 -0
- package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
- package/src/0-path-value-core/PathController.ts +1 -0
- package/src/0-path-value-core/PathValueCommitter.ts +565 -0
- package/src/0-path-value-core/PathValueController.ts +231 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
- package/src/0-path-value-core/debugLogs.ts +90 -0
- package/src/0-path-value-core/pathValueArchives.ts +483 -0
- package/src/0-path-value-core/pathValueCore.ts +2217 -0
- package/src/1-path-client/RemoteWatcher.ts +558 -0
- package/src/1-path-client/pathValueClientWatcher.ts +702 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
- package/src/2-proxy/archiveMoveHarness.ts +376 -0
- package/src/2-proxy/garbageCollection.ts +753 -0
- package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
- package/src/2-proxy/pathValueProxy.ts +139 -0
- package/src/2-proxy/schema2.ts +518 -0
- package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
- package/src/3-path-functions/PathFunctionRunner.ts +619 -0
- package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
- package/src/3-path-functions/deployBlock.ts +10 -0
- package/src/3-path-functions/deployCheck.ts +7 -0
- package/src/3-path-functions/deployMain.ts +160 -0
- package/src/3-path-functions/pathFunctionLoader.ts +282 -0
- package/src/3-path-functions/syncSchema.ts +475 -0
- package/src/3-path-functions/tests/functionsTest.ts +135 -0
- package/src/3-path-functions/tests/rejectTest.ts +77 -0
- package/src/4-dom/css.tsx +29 -0
- package/src/4-dom/cssTypes.d.ts +212 -0
- package/src/4-dom/qreact.tsx +2322 -0
- package/src/4-dom/qreactTest.tsx +417 -0
- package/src/4-querysub/Querysub.ts +877 -0
- package/src/4-querysub/QuerysubController.ts +620 -0
- package/src/4-querysub/copyEvent.ts +0 -0
- package/src/4-querysub/permissions.ts +289 -0
- package/src/4-querysub/permissionsShared.ts +1 -0
- package/src/4-querysub/querysubPrediction.ts +525 -0
- package/src/5-diagnostics/FullscreenModal.tsx +67 -0
- package/src/5-diagnostics/GenericFormat.tsx +165 -0
- package/src/5-diagnostics/Modal.tsx +79 -0
- package/src/5-diagnostics/Table.tsx +183 -0
- package/src/5-diagnostics/TimeGrouper.tsx +114 -0
- package/src/5-diagnostics/diskValueAudit.ts +216 -0
- package/src/5-diagnostics/memoryValueAudit.ts +442 -0
- package/src/5-diagnostics/nodeMetadata.ts +135 -0
- package/src/5-diagnostics/qreactDebug.tsx +309 -0
- package/src/5-diagnostics/shared.ts +26 -0
- package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
- package/src/TestController.ts +35 -0
- package/src/allowclient.flag +0 -0
- package/src/bits.ts +86 -0
- package/src/buffers.ts +69 -0
- package/src/config.ts +53 -0
- package/src/config2.ts +48 -0
- package/src/diagnostics/ActionsHistory.ts +56 -0
- package/src/diagnostics/NodeViewer.tsx +503 -0
- package/src/diagnostics/SizeLimiter.ts +62 -0
- package/src/diagnostics/TimeDebug.tsx +18 -0
- package/src/diagnostics/benchmark.ts +139 -0
- package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
- package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
- package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
- package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
- package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
- package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
- package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
- package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
- package/src/diagnostics/heapTag.ts +13 -0
- package/src/diagnostics/listenOnDebugger.ts +77 -0
- package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
- package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
- package/src/diagnostics/logs/ansiFormat.ts +108 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
- package/src/diagnostics/logs/diskLogger.ts +305 -0
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
- package/src/diagnostics/logs/logGitHashes.ts +30 -0
- package/src/diagnostics/managementPages.tsx +289 -0
- package/src/diagnostics/periodic.ts +89 -0
- package/src/diagnostics/runSaturationTest.ts +416 -0
- package/src/diagnostics/satSchema.ts +64 -0
- package/src/diagnostics/trackResources.ts +82 -0
- package/src/diagnostics/watchdog.ts +55 -0
- package/src/errors.ts +132 -0
- package/src/forceProduction.ts +3 -0
- package/src/fs.ts +72 -0
- package/src/heapDumps.ts +666 -0
- package/src/https.ts +2 -0
- package/src/inject.ts +1 -0
- package/src/library-components/ATag.tsx +84 -0
- package/src/library-components/Button.tsx +344 -0
- package/src/library-components/ButtonSelector.tsx +64 -0
- package/src/library-components/DropdownCustom.tsx +151 -0
- package/src/library-components/DropdownSelector.tsx +32 -0
- package/src/library-components/Input.tsx +334 -0
- package/src/library-components/InputLabel.tsx +198 -0
- package/src/library-components/InputPicker.tsx +125 -0
- package/src/library-components/LazyComponent.tsx +62 -0
- package/src/library-components/MeasureHeightCSS.tsx +48 -0
- package/src/library-components/MeasuredDiv.tsx +47 -0
- package/src/library-components/ShowMore.tsx +51 -0
- package/src/library-components/SyncedController.ts +171 -0
- package/src/library-components/TimeRangeSelector.tsx +407 -0
- package/src/library-components/URLParam.ts +263 -0
- package/src/library-components/colors.tsx +14 -0
- package/src/library-components/drag.ts +114 -0
- package/src/library-components/icons.tsx +692 -0
- package/src/library-components/niceStringify.ts +50 -0
- package/src/library-components/renderToString.ts +52 -0
- package/src/misc/PromiseRace.ts +101 -0
- package/src/misc/color.ts +30 -0
- package/src/misc/getParentProcessId.cs +53 -0
- package/src/misc/getParentProcessId.ts +53 -0
- package/src/misc/hash.ts +83 -0
- package/src/misc/ipPong.js +13 -0
- package/src/misc/networking.ts +2 -0
- package/src/misc/random.ts +45 -0
- package/src/misc.ts +19 -0
- package/src/noserverhotreload.flag +0 -0
- package/src/path.ts +226 -0
- package/src/persistentLocalStore.ts +37 -0
- package/src/promise.ts +15 -0
- package/src/server.ts +73 -0
- package/src/src.d.ts +1 -0
- package/src/test/heapProcess.ts +36 -0
- package/src/test/mongoSatTest.tsx +55 -0
- package/src/test/satTest.ts +193 -0
- package/src/test/test.tsx +552 -0
- package/src/zip.ts +92 -0
- package/src/zipThreaded.ts +106 -0
- package/src/zipThreadedWorker.js +19 -0
- package/tsconfig.json +27 -0
- package/yarnSpec.txt +56 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { getArchives } from "../-a-archives/archives";
|
|
3
|
+
import { getDomain, isDevDebugbreak, isNoNetwork } from "../config";
|
|
4
|
+
import { measureWrap } from "socket-function/src/profiling/measure";
|
|
5
|
+
import { isNode, sha256Hash, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
6
|
+
import { errorToUndefined, errorToUndefinedSilent, ignoreErrors, logErrors, timeoutToUndefined, timeoutToUndefinedSilent } from "../errors";
|
|
7
|
+
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
8
|
+
import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
9
|
+
import { getNodeId, getNodeIdDomain, getNodeIdFromLocation } from "socket-function/src/nodeCache";
|
|
10
|
+
import { lazy } from "socket-function/src/caching";
|
|
11
|
+
import { shuffle } from "../misc/random";
|
|
12
|
+
import { blue, green, magenta, red, yellow } from "socket-function/src/formatting/logColors";
|
|
13
|
+
import debugbreak from "debugbreak";
|
|
14
|
+
import { getArchivesBackblaze, getBackblazePath } from "../-a-archives/archivesBackBlaze";
|
|
15
|
+
import { PromiseObj } from "../promise";
|
|
16
|
+
import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
|
|
17
|
+
import { isClient, isServer } from "../config2";
|
|
18
|
+
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
19
|
+
import { decodeNodeId, decodeNodeIdAssert, getMachineId } from "../-a-auth/certs";
|
|
20
|
+
import { getPublicIP } from "../misc/networking";
|
|
21
|
+
|
|
22
|
+
import dns from "dns/promises";
|
|
23
|
+
import { isDefined } from "../misc";
|
|
24
|
+
import { diskLog } from "../diagnostics/logs/diskLogger";
|
|
25
|
+
|
|
26
|
+
let HEARTBEAT_INTERVAL = timeInMinute * 5;
|
|
27
|
+
// Interval which we check other heartbeats
|
|
28
|
+
let CHECK_INTERVAL = HEARTBEAT_INTERVAL;
|
|
29
|
+
// If the heartbeat is older than thing, it fails the dead check
|
|
30
|
+
let DEAD_THRESHOLD = HEARTBEAT_INTERVAL * 2;
|
|
31
|
+
// If we find another node is dead this number of checks, we remove it from storage, and tell
|
|
32
|
+
// all other nodes to remove from their memory.
|
|
33
|
+
let DEAD_CHECK_COUNT = 4;
|
|
34
|
+
// If we find we are unable to write our heartbeat, we have to kill our own process. Otherwise it may
|
|
35
|
+
// be too out of sync, and might commit unverified data to the disk.
|
|
36
|
+
let SUICIDE_HEARTBEAT_THRESHOLD = timeInMinute * 15;
|
|
37
|
+
|
|
38
|
+
let CLIENTSIDE_POLL_RATE = timeInMinute * 5;
|
|
39
|
+
|
|
40
|
+
let MEMORY_AUDIT_RATE = timeInMinute * 5;
|
|
41
|
+
let MEMORY_AUDIT_COUNT = 3;
|
|
42
|
+
|
|
43
|
+
let shutdown = false;
|
|
44
|
+
|
|
45
|
+
const archives = lazy(() => getArchives("nodes/"));
|
|
46
|
+
|
|
47
|
+
let logging = true;
|
|
48
|
+
export function enableNodeDiscoveryLogging() {
|
|
49
|
+
logging = true;
|
|
50
|
+
}
|
|
51
|
+
export function isNodeDiscoveryLogging() {
|
|
52
|
+
return logging;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const getOurNodeId = getOwnNodeId;
|
|
56
|
+
export const getOurMachineId = getOwnNodeId;
|
|
57
|
+
export const getOurNodeIdAssert = getOwnNodeIdAssert;
|
|
58
|
+
|
|
59
|
+
export const SPECIAL_NODE_ID_FOR_UNMOUNTED_NODE = "SPECIAL_NODE_ID_FOR_UNMOUNTED_NODE";
|
|
60
|
+
export function getOwnNodeId(): string {
|
|
61
|
+
let nodeId = SocketFunction.mountedNodeId;
|
|
62
|
+
if (!nodeId) {
|
|
63
|
+
return SPECIAL_NODE_ID_FOR_UNMOUNTED_NODE;
|
|
64
|
+
}
|
|
65
|
+
return nodeId;
|
|
66
|
+
}
|
|
67
|
+
export function getOwnNodeIdAssert(): string {
|
|
68
|
+
let nodeId = SocketFunction.mountedNodeId;
|
|
69
|
+
if (!nodeId) {
|
|
70
|
+
throw new Error(`Node must be mounted before nodeId is accessed`);
|
|
71
|
+
}
|
|
72
|
+
return nodeId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const getOwnThreadId = lazy(() => {
|
|
76
|
+
return decodeNodeIdAssert(getOwnNodeIdAssert()).threadId;
|
|
77
|
+
});
|
|
78
|
+
export const getOwnMachineId = lazy(() => {
|
|
79
|
+
return decodeNodeIdAssert(getOwnNodeIdAssert()).machineId;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export function isOwnNodeId(nodeId: string): boolean {
|
|
83
|
+
if (
|
|
84
|
+
nodeId === getOwnNodeId()
|
|
85
|
+
|| nodeId === SPECIAL_NODE_ID_FOR_UNMOUNTED_NODE
|
|
86
|
+
) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// If it's 127.0.0.1, and on the same port as us, it is us.
|
|
90
|
+
let obj = decodeNodeId(nodeId);
|
|
91
|
+
if (obj && obj.domain === "127-0-0-1." + getDomain() && obj.port === decodeNodeId(getOwnNodeId())?.port) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let nodeOverrides: string[] | undefined;
|
|
99
|
+
let beforeGetNodeAllId = async () => { };
|
|
100
|
+
export async function getAllNodeIds() {
|
|
101
|
+
if (nodeOverrides) {
|
|
102
|
+
return nodeOverrides;
|
|
103
|
+
}
|
|
104
|
+
await beforeGetNodeAllId();
|
|
105
|
+
return Array.from(allNodeIds2);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function syncNodesNow() {
|
|
109
|
+
await syncArchives();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** NOTE: You should try to use watchDeltaNodeIds instead of this. */
|
|
113
|
+
export function watchNodeIds(callback: (nodeIds: string[]) => void) {
|
|
114
|
+
function call() {
|
|
115
|
+
callback(Array.from(allNodeIds2));
|
|
116
|
+
}
|
|
117
|
+
nodeIdWatchers.push(call);
|
|
118
|
+
call();
|
|
119
|
+
}
|
|
120
|
+
export function watchDeltaNodeIds(callback: (config: { newNodeIds: string[]; removedNodeIds: string[] }) => void) {
|
|
121
|
+
let prevNodeIds: string[] = [];
|
|
122
|
+
watchNodeIds(nodeIds => {
|
|
123
|
+
let newNodeIds = nodeIds.filter(nodeId => !prevNodeIds.includes(nodeId));
|
|
124
|
+
let removedNodeIds = prevNodeIds.filter(nodeId => !nodeIds.includes(nodeId));
|
|
125
|
+
if (newNodeIds.length > 0 || removedNodeIds.length > 0) {
|
|
126
|
+
callback({ newNodeIds, removedNodeIds });
|
|
127
|
+
}
|
|
128
|
+
prevNodeIds = nodeIds;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let allNodeIds2 = new Set<string>();
|
|
133
|
+
function getAllNodesHash() {
|
|
134
|
+
// Always include ourselves in the hash
|
|
135
|
+
let sortedNodes = Array.from(allNodeIds2);
|
|
136
|
+
sortedNodes.sort();
|
|
137
|
+
return sha256Hash(JSON.stringify(sortedNodes));
|
|
138
|
+
}
|
|
139
|
+
function addNodeId(nodeId: string) {
|
|
140
|
+
addNodeIdBase(nodeId);
|
|
141
|
+
if (isNode() && isDevDebugbreak()) {
|
|
142
|
+
let obj = decodeNodeId(nodeId);
|
|
143
|
+
if (obj) {
|
|
144
|
+
let localNodeId = getNodeId("127-0-0-1." + getDomain(), obj.port);
|
|
145
|
+
addNodeIdBase(localNodeId);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function addNodeIdBase(nodeId: string) {
|
|
150
|
+
if (allNodeIds2.has(nodeId)) return;
|
|
151
|
+
if (logging) {
|
|
152
|
+
console.log(blue(`Discovered node ${nodeId}`));
|
|
153
|
+
}
|
|
154
|
+
allNodeIds2.add(nodeId);
|
|
155
|
+
onNodesChanged();
|
|
156
|
+
}
|
|
157
|
+
function removeNodeIds(nodeIds: string[]) {
|
|
158
|
+
diskLog("removeNodeIds", { nodeIds });
|
|
159
|
+
nodeIds = nodeIds.filter(nodeId => allNodeIds2.has(nodeId));
|
|
160
|
+
if (nodeIds.length === 0) return;
|
|
161
|
+
for (let nodeId of nodeIds) {
|
|
162
|
+
if (logging) {
|
|
163
|
+
console.log(red(`Removed node directly ${nodeId}`));
|
|
164
|
+
}
|
|
165
|
+
allNodeIds2.delete(nodeId);
|
|
166
|
+
}
|
|
167
|
+
onNodesChanged();
|
|
168
|
+
}
|
|
169
|
+
function setNodeIds(nodeIds: string[]) {
|
|
170
|
+
diskLog("setNodeIds", { nodeIds });
|
|
171
|
+
// Also try all localhost ports, if we are in dev mode
|
|
172
|
+
if (isNode() && isDevDebugbreak()) {
|
|
173
|
+
let ports = new Set(nodeIds.map(nodeId => decodeNodeId(nodeId)?.port).filter(isDefined));
|
|
174
|
+
for (let port of ports) {
|
|
175
|
+
let localNodeId = getNodeId("127-0-0-1." + getDomain(), port);
|
|
176
|
+
nodeIds.push(localNodeId);
|
|
177
|
+
}
|
|
178
|
+
nodeIds = Array.from(new Set(nodeIds));
|
|
179
|
+
}
|
|
180
|
+
let newNodeIds = nodeIds.filter(nodeId => !allNodeIds2.has(nodeId));
|
|
181
|
+
let newIds = new Set(nodeIds);
|
|
182
|
+
let removedNodeIds = Array.from(allNodeIds2).filter(nodeId => !newIds.has(nodeId));
|
|
183
|
+
if (newNodeIds.length === 0 && removedNodeIds.length === 0) return;
|
|
184
|
+
if (logging) {
|
|
185
|
+
for (let nodeId of newNodeIds) {
|
|
186
|
+
console.log(blue(`Discovered node ${nodeId}`));
|
|
187
|
+
}
|
|
188
|
+
for (let nodeId of removedNodeIds) {
|
|
189
|
+
console.log(red(`Removed node from setNodeIds ${nodeId}`));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
allNodeIds2 = new Set(nodeIds);
|
|
193
|
+
onNodesChanged();
|
|
194
|
+
}
|
|
195
|
+
let nodeIdWatchers: (() => void)[] = [];
|
|
196
|
+
function onNodesChanged() {
|
|
197
|
+
for (let watcher of nodeIdWatchers) {
|
|
198
|
+
try {
|
|
199
|
+
watcher();
|
|
200
|
+
} catch (e: any) {
|
|
201
|
+
console.log(red(`Ignoring error from callback ${e.stack}`));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let rootDiscoveryNodeId = "";
|
|
207
|
+
export function configRootDiscoveryLocation(config: {
|
|
208
|
+
domain: string;
|
|
209
|
+
port: number;
|
|
210
|
+
}) {
|
|
211
|
+
rootDiscoveryNodeId = getNodeId(config.domain, config.port);
|
|
212
|
+
}
|
|
213
|
+
/** NOTE: Can also be called serverside, if configRootDiscoveryLocation is called (otherwise can always be called clientside). */
|
|
214
|
+
export function getBrowserUrlNode() {
|
|
215
|
+
if (!isClient()) throw new Error(`getBrowserUrlNode can only be called when isClient()`);
|
|
216
|
+
let rootLocation = rootDiscoveryNodeId;
|
|
217
|
+
if (!rootLocation && !isNode()) {
|
|
218
|
+
rootLocation = getNodeIdFromLocation();
|
|
219
|
+
}
|
|
220
|
+
if (!rootLocation) {
|
|
221
|
+
throw new Error(`configRootDiscoveryLocation wasn't called, and ---client appears to have been used (isNode() and isClient()). Call configRootDiscoveryLocation with the domain and port of the server you wish to use (ex, querysub.com:1111)`);
|
|
222
|
+
}
|
|
223
|
+
return rootLocation;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let seenNodesForEvilDNS = new Set<string>();
|
|
227
|
+
async function syncArchives() {
|
|
228
|
+
if (isServer()) {
|
|
229
|
+
// Make sure we are present
|
|
230
|
+
await writeHeartbeat();
|
|
231
|
+
let nodeIds = await archives().find("");
|
|
232
|
+
console.log(green(`Syncing archives`), { nodeIds });
|
|
233
|
+
setNodeIds(nodeIds);
|
|
234
|
+
} else {
|
|
235
|
+
if (isNoNetwork() || !isNode()) {
|
|
236
|
+
// NOTE: If no network, our trust source might be different, so we can't talk to regular nodes,
|
|
237
|
+
// and instead have to only talk to HTTP nodes
|
|
238
|
+
setNodeIds([getBrowserUrlNode()]);
|
|
239
|
+
} else {
|
|
240
|
+
// If on the network, NetworkTrust2 should sync the trusted machines from backblaze, so we should be
|
|
241
|
+
// able to talk to any nodes.
|
|
242
|
+
// - If they user is using --client they only want to talk to querysub nodes. There might be multiple,
|
|
243
|
+
// which cloudflare will proxy, HOWEVER, it is more efficient to directly access the node list, which
|
|
244
|
+
// will be better for load balancing and updating on failure than the cloudflare proxying... probably.
|
|
245
|
+
setNodeIds(await NodeDiscoveryController.nodes[getBrowserUrlNode()].getAllNodeIds());
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function runHeartbeatAuditLoop() {
|
|
251
|
+
await onNodeDiscoveryReady();
|
|
252
|
+
let deadCount = new Map<string, number>();
|
|
253
|
+
await runInfinitePollCallAtStart(CHECK_INTERVAL, async () => {
|
|
254
|
+
if (shutdown) return;
|
|
255
|
+
// Wait a bit longer, to try to prevent all nodes from synchronizing their audit times.
|
|
256
|
+
await delay(HEARTBEAT_INTERVAL * Math.random() * 0.1);
|
|
257
|
+
//console.log(magenta(`Auditing node list`));
|
|
258
|
+
|
|
259
|
+
let deadTime = Date.now() - DEAD_THRESHOLD;
|
|
260
|
+
let nodeIds = await archives().find("");
|
|
261
|
+
// Should be unusual, but... if we find new nodes, add them, and let everyone else know?
|
|
262
|
+
for (let nodeId of nodeIds) {
|
|
263
|
+
if (!allNodeIds2.has(nodeId)) {
|
|
264
|
+
console.log(yellow(`Found unexpected node, adding it and broadcasting it, ${nodeId}`));
|
|
265
|
+
addNodeId(nodeId);
|
|
266
|
+
for (let nodeId of allNodeIds2) {
|
|
267
|
+
if (isOwnNodeId(nodeId)) continue;
|
|
268
|
+
ignoreErrors(NodeDiscoveryController.nodes[nodeId].addNode(nodeId));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
let removedNodeIds: string[] = [];
|
|
273
|
+
for (let nodeId of nodeIds) {
|
|
274
|
+
let lastTime = Number((await archives().get(nodeId))?.toString()) || 0;
|
|
275
|
+
if (lastTime < deadTime) {
|
|
276
|
+
// Increment the dead count
|
|
277
|
+
let count = deadCount.get(nodeId) || 0;
|
|
278
|
+
count++;
|
|
279
|
+
deadCount.set(nodeId, count);
|
|
280
|
+
if (count >= DEAD_CHECK_COUNT) {
|
|
281
|
+
removedNodeIds.push(nodeId);
|
|
282
|
+
console.log(yellow(`Node ${nodeId} was found to be dead, removing from node list. Last heartbeat at ${formatDateTime(lastTime)}, dead threshold at ${formatDateTime(deadTime)}`));
|
|
283
|
+
await archives().del(nodeId);
|
|
284
|
+
deadCount.delete(nodeId);
|
|
285
|
+
} else {
|
|
286
|
+
console.log(yellow(`Node ${nodeId} was found to be dead, last heartbeat at ${formatDateTime(lastTime)} < dead threshold at ${formatDateTime(deadTime)}, dead count ${count}/${DEAD_CHECK_COUNT}. Total nodes seen ${nodeIds.length}`));
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
deadCount.delete(nodeId);
|
|
290
|
+
diskLog("Read node heartbeat", { nodeId, lastTime });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
removeNodeIds(removedNodeIds);
|
|
295
|
+
|
|
296
|
+
// Broadcast to all nodes the removal
|
|
297
|
+
for (let nodeId of allNodeIds2) {
|
|
298
|
+
if (isOwnNodeId(nodeId)) continue;
|
|
299
|
+
ignoreErrors(NodeDiscoveryController.nodes[nodeId].removeNodes(removedNodeIds));
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function isOutOfSync() {
|
|
305
|
+
let auditNodes = shuffle(Array.from(allNodeIds2), Date.now()).slice(0, MEMORY_AUDIT_COUNT);
|
|
306
|
+
for (let nodeId of auditNodes) {
|
|
307
|
+
if (nodeId === getOwnNodeId()) continue;
|
|
308
|
+
|
|
309
|
+
let stableWait = timeInSecond * 30;
|
|
310
|
+
while (true) {
|
|
311
|
+
let hash = await errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].getAllNodesHash());
|
|
312
|
+
if (!hash) break;
|
|
313
|
+
let ownHash = getAllNodesHash();
|
|
314
|
+
if (hash === ownHash) break;
|
|
315
|
+
await delay(stableWait);
|
|
316
|
+
let hash2 = await errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].getAllNodesHash());
|
|
317
|
+
if (!hash2) break;
|
|
318
|
+
if (hash === hash2) {
|
|
319
|
+
console.log(yellow(`Node discovery is out of sync, syncing with disk. Told by node ${nodeId}, our hash of ${ownHash} is wrong and should be ${hash2}`));
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
// Otherwise, the hash changed, so... we maybe we just caught it while it was changing.
|
|
323
|
+
// Wait less time, to try to catch up with the rate of change
|
|
324
|
+
stableWait /= 2;
|
|
325
|
+
if (stableWait < timeInSecond) {
|
|
326
|
+
// It is changing so fast we better just sync with the disk
|
|
327
|
+
console.log(yellow(`Node discovery is changing fast and likely out of sync, resyncing with disk. Told by node ${nodeId}`));
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function runMemoryAuditLoop() {
|
|
335
|
+
await onNodeDiscoveryReady();
|
|
336
|
+
runInfinitePoll(MEMORY_AUDIT_RATE, async () => {
|
|
337
|
+
// In retrospec, reading all nodes every 5 minutes is fine. It costs about 0.035 USD
|
|
338
|
+
// per month to do this, which... is a lot less than the server to run this will cost!
|
|
339
|
+
//if (await isOutOfSync()) {
|
|
340
|
+
await syncArchives();
|
|
341
|
+
//}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function writeHeartbeat() {
|
|
346
|
+
if (shutdown) return;
|
|
347
|
+
let now = Date.now();
|
|
348
|
+
console.log(green(`Writing heartbeat ${formatDateTime(now)}`));
|
|
349
|
+
await archives().set(getOwnNodeId(), Buffer.from(now + ""));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function runMainSyncLoops(discoveryReady: PromiseObj<void>) {
|
|
353
|
+
await syncArchives();
|
|
354
|
+
|
|
355
|
+
discoveryReady.resolve();
|
|
356
|
+
|
|
357
|
+
// We can't heartbeat until we mount
|
|
358
|
+
await SocketFunction.mountPromise;
|
|
359
|
+
|
|
360
|
+
// We have to write before we call NodeDiscoveryController.addNode
|
|
361
|
+
await writeHeartbeat();
|
|
362
|
+
await syncArchives();
|
|
363
|
+
let selfNodeId = SocketFunction.mountedNodeId;
|
|
364
|
+
if (!allNodeIds2.has(selfNodeId)) {
|
|
365
|
+
throw new Error(`Failed sanity check, our node id didn't appear in archives after we just wrote it? Missing ${SocketFunction.mountedNodeId}`);
|
|
366
|
+
}
|
|
367
|
+
// NOTE: Our first broadcast is special
|
|
368
|
+
await Promise.allSettled(Array.from(allNodeIds2).map(async nodeId => {
|
|
369
|
+
if (isOwnNodeId(nodeId)) return;
|
|
370
|
+
// Ignore errors, but wait a bit, so hopefully 99.99% of the time we can be certain
|
|
371
|
+
// all other nodes know our node id at this point.
|
|
372
|
+
await timeoutToUndefinedSilent(timeInSecond * 5, errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].addNode(getOwnNodeId())));
|
|
373
|
+
}));
|
|
374
|
+
|
|
375
|
+
await runInfinitePoll(HEARTBEAT_INTERVAL, async function nodeDiscoverHeartbeat() {
|
|
376
|
+
// If we waited too long, other nodes might think we are dead. In which case, we SHOULD terminate.
|
|
377
|
+
if (!isNoNetwork()) {
|
|
378
|
+
// FIRST, verify we didn't delay too long (to make sure we kill any nodes that were disconnected
|
|
379
|
+
// from the internet for too long)
|
|
380
|
+
let lastTime = Number((await archives().get(selfNodeId))?.toString());
|
|
381
|
+
let suicideThreshold = Date.now() - SUICIDE_HEARTBEAT_THRESHOLD;
|
|
382
|
+
if (!lastTime || lastTime < suicideThreshold) {
|
|
383
|
+
if (!lastTime) {
|
|
384
|
+
console.error(red(`Self node was removed due to not heartbeating. Terminating self process, as it likely has very stale data.`));
|
|
385
|
+
} else {
|
|
386
|
+
console.error(red(`Self node was has very old heartbeat. Terminating self process, as it likely has very stale data.`));
|
|
387
|
+
}
|
|
388
|
+
process.exit();
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
// We DO have to re-broadcast, otherwise no one will know we exist
|
|
392
|
+
for (let nodeId of allNodeIds2) {
|
|
393
|
+
if (isOwnNodeId(nodeId)) continue;
|
|
394
|
+
// Ignore errors, as nodes die often, and we only want to log once - when we actually remove the node id.
|
|
395
|
+
ignoreErrors(NodeDiscoveryController.nodes[nodeId].addNode(getOwnNodeId()));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
await writeHeartbeat();
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export async function onNodeDiscoveryReady() {
|
|
404
|
+
// NOTE: This isn't really early enough, especially in the browser, because a lot of calls are created
|
|
405
|
+
// immediately on render. But... it should be good enough for now.
|
|
406
|
+
await waitForFirstTimeSync();
|
|
407
|
+
await getAllNodeIds();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (isServer()) {
|
|
411
|
+
let discoveryReady = new PromiseObj<void>();
|
|
412
|
+
beforeGetNodeAllId = async () => {
|
|
413
|
+
await discoveryReady.promise;
|
|
414
|
+
};
|
|
415
|
+
setImmediate(() => {
|
|
416
|
+
logErrors(runHeartbeatAuditLoop());
|
|
417
|
+
logErrors(runMemoryAuditLoop());
|
|
418
|
+
// NOTE: We used to wait until we mounted, but... we should be able to find nodes
|
|
419
|
+
// before we mount, right? (And what if we never mount?)
|
|
420
|
+
runMainSyncLoops(discoveryReady).catch(e => {
|
|
421
|
+
discoveryReady.reject(e);
|
|
422
|
+
logErrors(Promise.reject(e));
|
|
423
|
+
});
|
|
424
|
+
// // Wait until we mount
|
|
425
|
+
// void SocketFunction.mountPromise.finally(async () => {
|
|
426
|
+
|
|
427
|
+
// });
|
|
428
|
+
});
|
|
429
|
+
} else {
|
|
430
|
+
setImmediate(() => {
|
|
431
|
+
if (isNode()) {
|
|
432
|
+
// Just get the archives, syncing again if we haven't synced in a while
|
|
433
|
+
let lastGetTime = 0;
|
|
434
|
+
beforeGetNodeAllId = async () => {
|
|
435
|
+
let lastGetThreshold = lastGetTime + CLIENTSIDE_POLL_RATE;
|
|
436
|
+
if (Date.now() > lastGetThreshold) {
|
|
437
|
+
lastGetTime = Date.now();
|
|
438
|
+
await syncArchives();
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
} else {
|
|
442
|
+
let discoveryReady = new PromiseObj<void>();
|
|
443
|
+
let browserNodeId = getBrowserUrlNode();
|
|
444
|
+
let nodes = [getBrowserUrlNode()];
|
|
445
|
+
logErrors((async function checkForLocalHost() {
|
|
446
|
+
// If there is a server at the localhost domain, AND, it's the same as the current server...
|
|
447
|
+
// use that domain instead. This is just an optimization, but makes a big difference
|
|
448
|
+
// when using management tools to scan the DB (otherwise the entire DB has to go through
|
|
449
|
+
// our router, which can easily cap it at 15MB/s, because rogers is terrible).
|
|
450
|
+
// - Only if isNoNetwork, otherwise it causes unnecessary lag.
|
|
451
|
+
let isNoNetwork = await NodeDiscoveryController.nodes[browserNodeId].isNoNetwork();
|
|
452
|
+
if (isNoNetwork && !location.search.includes("nolocalhost")) {
|
|
453
|
+
let url = new URL("https://" + browserNodeId);
|
|
454
|
+
url.hostname = "127-0-0-1." + url.hostname;
|
|
455
|
+
let localhostNode = url.host;
|
|
456
|
+
// NOTE: The timeout isn't that important, as this is only if no network, and only on page load.
|
|
457
|
+
// - A low timeout here causes failure to use the localhost ip sometimes. WHICH, often breaks
|
|
458
|
+
// authentication (as we are likely not authenticated for our public IP).
|
|
459
|
+
let localhostNodeId = await timeoutToUndefinedSilent(1000, NodeDiscoveryController.nodes[localhostNode].getNodeId());
|
|
460
|
+
let httpServerNodeId = await NodeDiscoveryController.nodes[browserNodeId].getNodeId();
|
|
461
|
+
if (localhostNodeId === httpServerNodeId) {
|
|
462
|
+
nodes[0] = localhostNode;
|
|
463
|
+
rootDiscoveryNodeId = localhostNode;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
allNodeIds2 = new Set(nodes);
|
|
468
|
+
discoveryReady.resolve();
|
|
469
|
+
})());
|
|
470
|
+
|
|
471
|
+
// Block until we finish checking for local host
|
|
472
|
+
beforeGetNodeAllId = async () => {
|
|
473
|
+
await discoveryReady.promise;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// NOTE: We run into TLS issues (as in, our servers use self signed certs), if we try to talk to just
|
|
477
|
+
// any node, so... we better just talk to the edge node
|
|
478
|
+
// - We COULD probably just use some special domain (maybe JUST the machine domain?), with limited wildcard
|
|
479
|
+
// certs (I think we can only wildcard a single depth anyways), and A records for the machines too...
|
|
480
|
+
// but... having all traffic route through an edge node is probably better anyways...
|
|
481
|
+
nodeOverrides = nodes;
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
/** Called on shutdown, to completely remove this node from discovery. */
|
|
488
|
+
export async function nodeDiscoveryShutdown() {
|
|
489
|
+
console.log(red(`Shutting down node discovery`));
|
|
490
|
+
shutdown = true;
|
|
491
|
+
if (isServer()) {
|
|
492
|
+
await archives().del(getOwnNodeId());
|
|
493
|
+
}
|
|
494
|
+
// Don't bother to remove the node from ourself, as we will be shutting down shortly
|
|
495
|
+
// (but do bother to tell other nodes!)
|
|
496
|
+
await Promise.allSettled(Array.from(allNodeIds2).map(async nodeId => {
|
|
497
|
+
if (isOwnNodeId(nodeId)) return;
|
|
498
|
+
await timeoutToUndefinedSilent(timeInSecond * 5, errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].removeNodes([getOwnNodeId()])));
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class NodeDiscoveryControllerBase {
|
|
504
|
+
public async addNode(nodeId: string) {
|
|
505
|
+
addNodeId(nodeId);
|
|
506
|
+
}
|
|
507
|
+
public async removeNodes(nodeIds: string[]) {
|
|
508
|
+
let callerId = SocketFunction.getCaller().nodeId;
|
|
509
|
+
console.log(`remote removeNode request`, { callerId, nodeIds });
|
|
510
|
+
removeNodeIds(nodeIds);
|
|
511
|
+
}
|
|
512
|
+
public async getAllNodesHash(): Promise<string> {
|
|
513
|
+
return getAllNodesHash();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public async getAllNodeIds(): Promise<string[]> {
|
|
517
|
+
return Array.from(allNodeIds2);
|
|
518
|
+
}
|
|
519
|
+
public async getNodeId() {
|
|
520
|
+
console.log(`Returning node id ${SocketFunction.mountedNodeId}`);
|
|
521
|
+
return SocketFunction.mountedNodeId;
|
|
522
|
+
}
|
|
523
|
+
public async isNoNetwork() {
|
|
524
|
+
return isNoNetwork();
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const NodeDiscoveryController = SocketFunction.register(
|
|
528
|
+
"NodeDiscoveryController-7991037e-fd9e-4085-b1db-52035487e72c",
|
|
529
|
+
new NodeDiscoveryControllerBase(),
|
|
530
|
+
() => ({
|
|
531
|
+
addNode: { hooks: [requiresNetworkTrustHook] },
|
|
532
|
+
removeNodes: { hooks: [requiresNetworkTrustHook] },
|
|
533
|
+
getAllNodesHash: { hooks: [requiresNetworkTrustHook] },
|
|
534
|
+
// Skip client hooks, so we don't block on authentication (IdentityController), as some of these functions
|
|
535
|
+
// are needed for authentication to finish!
|
|
536
|
+
getAllNodeIds: { noClientHooks: true },
|
|
537
|
+
getNodeId: { noClientHooks: true },
|
|
538
|
+
isNoNetwork: { noClientHooks: true },
|
|
539
|
+
}),
|
|
540
|
+
() => ({
|
|
541
|
+
|
|
542
|
+
})
|
|
543
|
+
);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Find nodes that implement a specific controller.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
6
|
+
import { SocketRegistered } from "socket-function/SocketFunctionTypes";
|
|
7
|
+
import { errorToUndefined, errorToUndefinedSilent, timeoutToUndefinedSilent } from "../errors";
|
|
8
|
+
import { getAllNodeIds } from "../-f-node-discovery/NodeDiscovery";
|
|
9
|
+
import { green, red, yellow } from "socket-function/src/formatting/logColors";
|
|
10
|
+
import { shuffle } from "../misc/random";
|
|
11
|
+
import { delay } from "socket-function/src/batching";
|
|
12
|
+
import debugbreak from "debugbreak";
|
|
13
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
14
|
+
import type { DebugFunctionShardInfo } from "../3-path-functions/PathFunctionRunner";
|
|
15
|
+
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
16
|
+
import { isNoNetwork } from "../config";
|
|
17
|
+
import { getDebuggerUrl } from "../diagnostics/listenOnDebugger";
|
|
18
|
+
import { hackDevtoolsWebsocketForward } from "./oneTimeForward";
|
|
19
|
+
|
|
20
|
+
let loadTime = Date.now();
|
|
21
|
+
|
|
22
|
+
// NOTE: If this becomes slow (because we are just trying all servers), we could start to store capabilities
|
|
23
|
+
// in PersistedStorage. However, I don't think it will be a problem for quite some time.
|
|
24
|
+
export async function getControllerNodeId(
|
|
25
|
+
controller: SocketRegistered<{}>,
|
|
26
|
+
quiet = false,
|
|
27
|
+
retryCount = 1,
|
|
28
|
+
initialTime = Date.now(),
|
|
29
|
+
): Promise<string | undefined> {
|
|
30
|
+
let nodeIdsToTest = await getAllNodeIds();
|
|
31
|
+
// Shuffle, so we aren't always using the same node!
|
|
32
|
+
nodeIdsToTest = shuffle(nodeIdsToTest, Date.now());
|
|
33
|
+
for (let nodeId of nodeIdsToTest) {
|
|
34
|
+
if (await doesNodeExposeController(nodeId, controller)) {
|
|
35
|
+
if (!quiet) {
|
|
36
|
+
let duration = Date.now() - initialTime;
|
|
37
|
+
console.log(green(`Resolved capability ${controller._classGuid} with node ${nodeId}, in ${formatTime(duration)}`));
|
|
38
|
+
}
|
|
39
|
+
return nodeId;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (retryCount > 0) {
|
|
43
|
+
await delay(2000);
|
|
44
|
+
return getControllerNodeId(controller, quiet, retryCount - 1, initialTime);
|
|
45
|
+
}
|
|
46
|
+
if (!quiet) {
|
|
47
|
+
console.warn(yellow(`Could not find a node that exposes controller ${controller._classGuid}, tried: ${JSON.stringify(nodeIdsToTest)}`));
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function getControllerNodeIdList(
|
|
53
|
+
controller: SocketRegistered<{}>,
|
|
54
|
+
): Promise<string[]> {
|
|
55
|
+
let nodeIdsToTest = await getAllNodeIds();
|
|
56
|
+
let passedNodeIds = new Set<string>();
|
|
57
|
+
for (let nodeId of nodeIdsToTest) {
|
|
58
|
+
if (await doesNodeExposeController(nodeId, controller)) {
|
|
59
|
+
passedNodeIds.add(nodeId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return Array.from(passedNodeIds);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function doesNodeExposeController(reconnectNodeId: string, controller: SocketRegistered<{}>): Promise<boolean> {
|
|
66
|
+
let exposedControllers = await timeoutToUndefinedSilent(10_000, NodeCapabilitiesController.nodes[reconnectNodeId].getExposedControllers());
|
|
67
|
+
return !!exposedControllers?.includes(controller._classGuid);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const startupTime = Date.now();
|
|
71
|
+
class NodeCapabilitiesControllerBase {
|
|
72
|
+
public async getExposedControllers() {
|
|
73
|
+
return Array.from(SocketFunction.exposedClasses);
|
|
74
|
+
}
|
|
75
|
+
public async getEntryPoint() {
|
|
76
|
+
return process.argv[1];
|
|
77
|
+
}
|
|
78
|
+
public async getStartupTime() {
|
|
79
|
+
return startupTime;
|
|
80
|
+
}
|
|
81
|
+
public async getMemoryUsage() {
|
|
82
|
+
return process.memoryUsage();
|
|
83
|
+
}
|
|
84
|
+
public async getFunctionRunnerShards() {
|
|
85
|
+
return getFunctionRunnerShards();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public async getInspectURL() {
|
|
89
|
+
return getDebuggerUrl();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public async exposeExternalDebugPortOnce(forExternalIP: string) {
|
|
93
|
+
// https://notdevtools.com/devtools/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:62448/22895aed-f8da-4dfb-8d50-e432a9c2d827
|
|
94
|
+
let debugURL = await this.getInspectURL();
|
|
95
|
+
|
|
96
|
+
const outerUrl = new URL(debugURL);
|
|
97
|
+
const wsParam = outerUrl.searchParams.get("ws");
|
|
98
|
+
if (!wsParam) throw new Error("No ws parameter found");
|
|
99
|
+
|
|
100
|
+
// Add ws:// protocol to make it parseable by URL
|
|
101
|
+
const innerUrl = new URL(`ws://${wsParam}`);
|
|
102
|
+
const port = innerUrl.port;
|
|
103
|
+
if (!port) throw new Error("No port found");
|
|
104
|
+
|
|
105
|
+
const portNumber = parseInt(port, 10);
|
|
106
|
+
if (isNaN(portNumber)) throw new Error("Invalid port number");
|
|
107
|
+
|
|
108
|
+
let { externalPort, cancel } = await hackDevtoolsWebsocketForward({ internalPort: portNumber, externalIP: forExternalIP });
|
|
109
|
+
lastExposed?.();
|
|
110
|
+
lastExposed = cancel;
|
|
111
|
+
return { externalPort, internalPort: portNumber, internalInspectURL: debugURL };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let lastExposed: (() => void) | undefined;
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
let getFunctionRunnerShards = () => [] as DebugFunctionShardInfo[];
|
|
118
|
+
export function set_debug_getFunctionRunnerShards(fnc: () => DebugFunctionShardInfo[]) {
|
|
119
|
+
getFunctionRunnerShards = fnc;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const NodeCapabilitiesController = SocketFunction.register(
|
|
123
|
+
"NodeCapabilitiesController-399b7067-75c3-4d92-8be3-8470bde32d3c",
|
|
124
|
+
new NodeCapabilitiesControllerBase(),
|
|
125
|
+
() => ({
|
|
126
|
+
getExposedControllers: {},
|
|
127
|
+
getEntryPoint: {},
|
|
128
|
+
getStartupTime: {},
|
|
129
|
+
getMemoryUsage: {},
|
|
130
|
+
getFunctionRunnerShards: {},
|
|
131
|
+
getInspectURL: { hooks: [requiresNetworkTrustHook] },
|
|
132
|
+
exposeExternalDebugPortOnce: { hooks: [requiresNetworkTrustHook] },
|
|
133
|
+
})
|
|
134
|
+
);
|