querysub 0.646.0 → 0.647.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/package.json
CHANGED
|
@@ -12,22 +12,26 @@ import { getClientNodeId, getNodeId, getNodeIdDomain, getNodeIdIP, getNodeIdLoca
|
|
|
12
12
|
import { decodeNodeId, getCommonName, getIdentityCA, getMachineId, getOwnMachineId, getPublicIdentifier, getThreadKeyCert, parseCert, sign, validateCertificate, verify } from "sliftutils/misc/https/certs";
|
|
13
13
|
import { getShortNumber } from "socket-function/src/bits";
|
|
14
14
|
import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
|
|
15
|
-
import { timeoutToError } from "../errors";
|
|
15
|
+
import { logErrors, timeoutToError } from "../errors";
|
|
16
16
|
import { delay } from "socket-function/src/batching";
|
|
17
17
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
18
18
|
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
19
19
|
import { red } from "socket-function/src/formatting/logColors";
|
|
20
20
|
import { isNode } from "typesafecss";
|
|
21
21
|
import { areNodeIdsEqual, getOwnNodeId, getOwnThreadId } from "../-f-node-discovery/NodeDiscovery";
|
|
22
|
-
import { timeInMinute, isIpDomain } from "socket-function/src/misc";
|
|
22
|
+
import { timeInMinute, isIpDomain, PromiseObj } from "socket-function/src/misc";
|
|
23
23
|
import { isClient, isServer } from "../config2";
|
|
24
24
|
import { getDomain } from "../config";
|
|
25
25
|
|
|
26
26
|
// NOTE: This used to be small, but we cache this, so it would mean a node on startup would time out, and then we would refuse to talk to it ever again. So... this can't be small
|
|
27
27
|
const MAX_CHANGE_IDENTITY_TIMEOUT = timeInMinute * 5;
|
|
28
|
+
// Such a large timeout because sometimes startup can be really slow, such as if our storage system has to fall back (as the storage system does retries, etc).
|
|
29
|
+
const MOUNT_WAIT_TIMEOUT = timeInMinute * 3;
|
|
28
30
|
|
|
29
31
|
let callerInfo = new Map<CallerContext, {
|
|
30
|
-
reconnectNodeId: string | undefined;
|
|
32
|
+
reconnectNodeId: string | Promise<string | Error> | undefined;
|
|
33
|
+
resolveReconnectNodeId?: (value: string | Error) => void;
|
|
34
|
+
certCommonName: string;
|
|
31
35
|
machineId: string;
|
|
32
36
|
cert: CertInfo;
|
|
33
37
|
pubKey: Buffer;
|
|
@@ -49,25 +53,40 @@ export function IdentityController_getReconnectNodeId(callerContext: CallerConte
|
|
|
49
53
|
if (allowUndefined) return undefined;
|
|
50
54
|
throw new Error(callerInfoErrorString);
|
|
51
55
|
}
|
|
52
|
-
|
|
56
|
+
let value = info.reconnectNodeId;
|
|
57
|
+
if (typeof value !== "string") return undefined;
|
|
58
|
+
return value;
|
|
53
59
|
}
|
|
54
|
-
export function IdentityController_getReconnectNodeIdAssert(callerContext: CallerContext): string {
|
|
60
|
+
export async function IdentityController_getReconnectNodeIdAssert(callerContext: CallerContext): Promise<string> {
|
|
55
61
|
if (!isClientNodeId(callerContext.nodeId)) {
|
|
56
62
|
return callerContext.nodeId;
|
|
57
63
|
}
|
|
58
|
-
let
|
|
59
|
-
if (!
|
|
64
|
+
let info = callerInfo.get(callerContext);
|
|
65
|
+
if (!info) {
|
|
66
|
+
throw new Error(callerInfoErrorString);
|
|
67
|
+
}
|
|
68
|
+
let valueBase = info.reconnectNodeId;
|
|
69
|
+
let value: string | Error | undefined;
|
|
70
|
+
if (valueBase instanceof Promise) {
|
|
71
|
+
value = await valueBase;
|
|
72
|
+
} else {
|
|
73
|
+
value = valueBase;
|
|
74
|
+
}
|
|
75
|
+
if (value instanceof Error) {
|
|
76
|
+
throw value;
|
|
77
|
+
}
|
|
78
|
+
if (!value) {
|
|
60
79
|
console.error(`Caller did not mount before connecting. This call requires the caller to be listening.`, {
|
|
61
80
|
callerNodeId: callerContext.nodeId,
|
|
62
81
|
});
|
|
63
82
|
throw new Error(`Caller did not mount before connecting. This call requires the caller to be listening.`);
|
|
64
83
|
}
|
|
65
|
-
return
|
|
84
|
+
return value;
|
|
66
85
|
}
|
|
67
86
|
export function IdentityController_getSecureIP(callerContext: CallerContext): string {
|
|
68
87
|
return getNodeIdIP(callerContext.nodeId);
|
|
69
88
|
}
|
|
70
|
-
export function IdentityController_getCurrentReconnectNodeIdAssert(): string {
|
|
89
|
+
export function IdentityController_getCurrentReconnectNodeIdAssert(): Promise<string> {
|
|
71
90
|
return IdentityController_getReconnectNodeIdAssert(SocketFunction.getCaller());
|
|
72
91
|
}
|
|
73
92
|
export function IdentityController_getCurrentReconnectNodeId(): string | undefined {
|
|
@@ -76,7 +95,9 @@ export function IdentityController_getCurrentReconnectNodeId(): string | undefin
|
|
|
76
95
|
|
|
77
96
|
export function debugNodeId(nodeId: string) {
|
|
78
97
|
let info = Array.from(callerInfo.entries()).find(x => x[0].nodeId === nodeId);
|
|
79
|
-
|
|
98
|
+
let value = info?.[1].reconnectNodeId;
|
|
99
|
+
if (typeof value !== "string") return nodeId;
|
|
100
|
+
return value;
|
|
80
101
|
};
|
|
81
102
|
(globalThis as any).debugNodeId = debugNodeId;
|
|
82
103
|
|
|
@@ -177,12 +198,21 @@ class IdentityControllerBase {
|
|
|
177
198
|
// Verify we even trust the issuer/cert pair
|
|
178
199
|
validateCertificate(getDomain(), payload.cert, payload.certIssuer);
|
|
179
200
|
|
|
180
|
-
|
|
201
|
+
// NOTE: We use the common name, and not getNodeIdIP... because anything connecting will need
|
|
202
|
+
// to use HTTPS, so connecting over the IP won't work! (unless they turn off certificate validation,
|
|
203
|
+
// which we shouldn't do...)
|
|
204
|
+
let certCommonName = getCommonName(payload.cert);
|
|
205
|
+
let reconnectNodeId: string | Promise<string | Error> | undefined;
|
|
206
|
+
let resolveReconnectNodeId: ((value: string | Error) => void) | undefined;
|
|
181
207
|
if (payload.mountedPort) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
reconnectNodeId =
|
|
208
|
+
reconnectNodeId = getNodeId(certCommonName, payload.mountedPort);
|
|
209
|
+
} else {
|
|
210
|
+
let pending = new PromiseObj<string | Error>();
|
|
211
|
+
reconnectNodeId = pending.promise;
|
|
212
|
+
resolveReconnectNodeId = pending.resolve;
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
pending.resolve(new Error(`Caller did not mount within ${formatTime(MOUNT_WAIT_TIMEOUT)} of connecting. This call requires the caller to be listening.`));
|
|
215
|
+
}, MOUNT_WAIT_TIMEOUT);
|
|
186
216
|
}
|
|
187
217
|
|
|
188
218
|
let pubKey = getPublicIdentifier(payload.cert);
|
|
@@ -191,7 +221,9 @@ class IdentityControllerBase {
|
|
|
191
221
|
callerInfo.set(caller, {
|
|
192
222
|
cert: { certPEM: payload.cert, issuerPEM: payload.certIssuer },
|
|
193
223
|
machineId,
|
|
224
|
+
certCommonName,
|
|
194
225
|
reconnectNodeId,
|
|
226
|
+
resolveReconnectNodeId,
|
|
195
227
|
pubKey,
|
|
196
228
|
pubKeyShort: getShortNumber(pubKey),
|
|
197
229
|
});
|
|
@@ -206,13 +238,28 @@ class IdentityControllerBase {
|
|
|
206
238
|
});
|
|
207
239
|
|
|
208
240
|
SocketFunction.onNextDisconnect(caller.nodeId, () => {
|
|
209
|
-
// NOTE: I don't really see any purpose of deleting from caller info. I don't think we're going to run out of memory because of too many callers authenticating.
|
|
241
|
+
// NOTE: I don't really see any purpose of deleting from caller info. I don't think we're going to run out of memory because of too many callers authenticating.
|
|
210
242
|
// However, logging here is useful as it allows us to complete the life cycle so we know how long a client was connected for.
|
|
211
243
|
console.info(`Disconnected client`, {
|
|
212
244
|
clientId: caller.nodeId,
|
|
213
245
|
});
|
|
214
246
|
});
|
|
215
247
|
}
|
|
248
|
+
|
|
249
|
+
@measureFnc
|
|
250
|
+
public async setMountedPort(mountedPort: number) {
|
|
251
|
+
const caller = SocketFunction.getCaller();
|
|
252
|
+
let info = callerInfo.get(caller);
|
|
253
|
+
if (!info) throw new Error(callerInfoErrorString);
|
|
254
|
+
let reconnectNodeId = getNodeId(info.certCommonName, mountedPort);
|
|
255
|
+
info.reconnectNodeId = reconnectNodeId;
|
|
256
|
+
info.resolveReconnectNodeId?.(reconnectNodeId);
|
|
257
|
+
console.info(`Caller mounted after connecting, set reconnect node id`, {
|
|
258
|
+
clientId: caller.nodeId,
|
|
259
|
+
reconnectNodeId,
|
|
260
|
+
mountedPort,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
216
263
|
}
|
|
217
264
|
|
|
218
265
|
const IdentityController = SocketFunction.register(
|
|
@@ -227,6 +274,7 @@ const IdentityController = SocketFunction.register(
|
|
|
227
274
|
// noClientHooks: true,
|
|
228
275
|
// noDefaultHooks: true,
|
|
229
276
|
},
|
|
277
|
+
setMountedPort: {},
|
|
230
278
|
})
|
|
231
279
|
);
|
|
232
280
|
|
|
@@ -250,6 +298,19 @@ const changeIdentityOnce = cacheWeak(async function changeIdentityOnce(connectio
|
|
|
250
298
|
IdentityController.nodes[nodeId].changeIdentity(signature, payload),
|
|
251
299
|
() => new Error(`Timeout calling changeIdentity for ${nodeId}`)
|
|
252
300
|
);
|
|
301
|
+
if (isServer() && !payload.mountedPort) {
|
|
302
|
+
logErrors((async () => {
|
|
303
|
+
await SocketFunction.mountPromise;
|
|
304
|
+
let mountedPort = getNodeIdLocation(SocketFunction.mountedNodeId)?.port;
|
|
305
|
+
if (!mountedPort) return;
|
|
306
|
+
console.info(`Mounted after connecting to ${nodeId}, sending mounted port ${mountedPort}`);
|
|
307
|
+
await timeoutToError(
|
|
308
|
+
MAX_CHANGE_IDENTITY_TIMEOUT,
|
|
309
|
+
IdentityController.nodes[nodeId].setMountedPort(mountedPort),
|
|
310
|
+
() => new Error(`Timeout calling setMountedPort for ${nodeId}`)
|
|
311
|
+
);
|
|
312
|
+
})());
|
|
313
|
+
}
|
|
253
314
|
});
|
|
254
315
|
let startupTime = Date.now();
|
|
255
316
|
async function identityHook(context: ClientHookContext) {
|
|
@@ -180,7 +180,7 @@ class AuthorityLookup {
|
|
|
180
180
|
isReady: boolean;
|
|
181
181
|
scheduledShutdownTime?: number;
|
|
182
182
|
}> {
|
|
183
|
-
let nodeId = IdentityController_getCurrentReconnectNodeIdAssert();
|
|
183
|
+
let nodeId = await IdentityController_getCurrentReconnectNodeIdAssert();
|
|
184
184
|
this.updatePaths(nodeId, config.spec, config.isReady, config.scheduledShutdownTime);
|
|
185
185
|
console.info(`Received network sync, returning our data`, {
|
|
186
186
|
nodeId,
|
|
@@ -211,7 +211,7 @@ class AuthorityLookup {
|
|
|
211
211
|
}, stopObj);
|
|
212
212
|
|
|
213
213
|
const onNodeRemoved = () => {
|
|
214
|
-
this.
|
|
214
|
+
this.removeNode(nodeId, "node removed from node discovery");
|
|
215
215
|
stopObj.stop = true;
|
|
216
216
|
this.connectTo.clear(nodeId);
|
|
217
217
|
this.disconnectWatch.clear(nodeId);
|
|
@@ -230,15 +230,20 @@ class AuthorityLookup {
|
|
|
230
230
|
let onDisconnect = () => {
|
|
231
231
|
SocketFunction.onNextDisconnect(nodeId, onDisconnect, "iKnowThatServerNodeIdsMayReconnect_andIHandleReconnections");
|
|
232
232
|
// NOTE: Will be re-added once we receive an updatePaths call (or the node will be removed by NodeDiscovery, in which case we will be entirely cleaned up).
|
|
233
|
-
this.
|
|
233
|
+
this.removeNode(nodeId, "socket disconnected");
|
|
234
234
|
};
|
|
235
235
|
SocketFunction.onNextDisconnect(nodeId, onDisconnect, "iKnowThatServerNodeIdsMayReconnect_andIHandleReconnections");
|
|
236
236
|
});
|
|
237
|
+
private removeNode(nodeId: string, reason: string) {
|
|
238
|
+
if (!this.topology.nodes.has(nodeId)) return;
|
|
239
|
+
this.topology.nodes.delete(nodeId);
|
|
240
|
+
console.log(`Removed authority topology entry for ${nodeId} (${this.topology.nodes.size} entries remaining). Reason: ${reason}`);
|
|
241
|
+
}
|
|
237
242
|
private async syncNodeNow(nodeId: string) {
|
|
238
243
|
try {
|
|
239
244
|
await this.syncNodeNowBase(nodeId);
|
|
240
|
-
} catch {
|
|
241
|
-
this.
|
|
245
|
+
} catch (e: any) {
|
|
246
|
+
this.removeNode(nodeId, `sync failed: ${String(e?.stack || e)}`);
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -66,7 +66,8 @@ class StorageLogsControllerBase {
|
|
|
66
66
|
let stores: ServerPathValue["stores"] = [];
|
|
67
67
|
try {
|
|
68
68
|
let routing = await controller.get2({ account, bucketName, path: ROUTING_FILE, sourceConfig: fabricatedSource, internal: true });
|
|
69
|
-
let
|
|
69
|
+
let routingParsed = routing && parseRoutingData(routing.data);
|
|
70
|
+
let sources = routingParsed && routingParsed.sources.map(normalizeSource) || [];
|
|
70
71
|
let seenNames = new Set<string>();
|
|
71
72
|
for (let source of sources) {
|
|
72
73
|
if (source.type !== "remote") continue;
|