querysub 0.646.0 → 0.648.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,28 +12,39 @@ 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 |
|
|
32
|
+
reconnectNodeId: string | Promise<string>;
|
|
33
|
+
reconnectNodeIdPromiseObj: PromiseObj<string> | undefined;
|
|
34
|
+
certCommonName: string;
|
|
31
35
|
machineId: string;
|
|
32
36
|
cert: CertInfo;
|
|
33
37
|
pubKey: Buffer;
|
|
34
38
|
pubKeyShort: number;
|
|
35
39
|
}>();
|
|
36
|
-
|
|
40
|
+
|
|
41
|
+
function getCallerInfoAssert(callerContext: CallerContext) {
|
|
42
|
+
let info = callerInfo.get(callerContext);
|
|
43
|
+
if (!info) {
|
|
44
|
+
throw new Error(`Internal error, caller did not updated their identity. Is this an HTTPS call (instead of a websocket call)? The caller should import the server controller, which imports this function, which will add a client hook to always update the identity`);
|
|
45
|
+
}
|
|
46
|
+
return info;
|
|
47
|
+
}
|
|
37
48
|
|
|
38
49
|
/** Gets the nodeId of the caller suitable for reconnecting (to the same process).
|
|
39
50
|
* - Also useful for logs, to identify a node on the network.
|
|
@@ -47,27 +58,24 @@ export function IdentityController_getReconnectNodeId(callerContext: CallerConte
|
|
|
47
58
|
let info = callerInfo.get(callerContext);
|
|
48
59
|
if (!info) {
|
|
49
60
|
if (allowUndefined) return undefined;
|
|
50
|
-
|
|
61
|
+
info = getCallerInfoAssert(callerContext);
|
|
62
|
+
}
|
|
63
|
+
if (typeof info.reconnectNodeId === "string") {
|
|
64
|
+
return info.reconnectNodeId;
|
|
51
65
|
}
|
|
52
|
-
return
|
|
66
|
+
return undefined;
|
|
53
67
|
}
|
|
54
|
-
export function IdentityController_getReconnectNodeIdAssert(callerContext: CallerContext): string {
|
|
68
|
+
export async function IdentityController_getReconnectNodeIdAssert(callerContext: CallerContext): Promise<string> {
|
|
55
69
|
if (!isClientNodeId(callerContext.nodeId)) {
|
|
56
70
|
return callerContext.nodeId;
|
|
57
71
|
}
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
console.error(`Caller did not mount before connecting. This call requires the caller to be listening.`, {
|
|
61
|
-
callerNodeId: callerContext.nodeId,
|
|
62
|
-
});
|
|
63
|
-
throw new Error(`Caller did not mount before connecting. This call requires the caller to be listening.`);
|
|
64
|
-
}
|
|
65
|
-
return reconnectId;
|
|
72
|
+
let info = getCallerInfoAssert(callerContext);
|
|
73
|
+
return await info.reconnectNodeId;
|
|
66
74
|
}
|
|
67
75
|
export function IdentityController_getSecureIP(callerContext: CallerContext): string {
|
|
68
76
|
return getNodeIdIP(callerContext.nodeId);
|
|
69
77
|
}
|
|
70
|
-
export function IdentityController_getCurrentReconnectNodeIdAssert(): string {
|
|
78
|
+
export function IdentityController_getCurrentReconnectNodeIdAssert(): Promise<string> {
|
|
71
79
|
return IdentityController_getReconnectNodeIdAssert(SocketFunction.getCaller());
|
|
72
80
|
}
|
|
73
81
|
export function IdentityController_getCurrentReconnectNodeId(): string | undefined {
|
|
@@ -76,7 +84,11 @@ export function IdentityController_getCurrentReconnectNodeId(): string | undefin
|
|
|
76
84
|
|
|
77
85
|
export function debugNodeId(nodeId: string) {
|
|
78
86
|
let info = Array.from(callerInfo.entries()).find(x => x[0].nodeId === nodeId);
|
|
79
|
-
|
|
87
|
+
let reconnectNodeId = info?.[1].reconnectNodeId;
|
|
88
|
+
if (typeof reconnectNodeId !== "string") {
|
|
89
|
+
return nodeId;
|
|
90
|
+
}
|
|
91
|
+
return reconnectNodeId;
|
|
80
92
|
};
|
|
81
93
|
(globalThis as any).debugNodeId = debugNodeId;
|
|
82
94
|
|
|
@@ -97,21 +109,19 @@ export function IdentityController_getMachineId(callerContext: CallerContext, al
|
|
|
97
109
|
let info = callerInfo.get(callerContext);
|
|
98
110
|
if (!info) {
|
|
99
111
|
if (allowEmpty) return "";
|
|
100
|
-
|
|
112
|
+
info = getCallerInfoAssert(callerContext);
|
|
101
113
|
}
|
|
102
114
|
return info.machineId;
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
export type CertInfo = { certPEM: Buffer | string; issuerPEM: Buffer | string; };
|
|
106
118
|
export function IdentityController_getCertInfo(callerContext: CallerContext): CertInfo {
|
|
107
|
-
let info =
|
|
108
|
-
if (!info) throw new Error(callerInfoErrorString);
|
|
119
|
+
let info = getCallerInfoAssert(callerContext);
|
|
109
120
|
return info.cert;
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
export function IdentityController_getPubKeyShort(callerContext: CallerContext): number {
|
|
113
|
-
let info =
|
|
114
|
-
if (!info) throw new Error(callerInfoErrorString);
|
|
124
|
+
let info = getCallerInfoAssert(callerContext);
|
|
115
125
|
return info.pubKeyShort;
|
|
116
126
|
}
|
|
117
127
|
export const IdentityController_getOwnPubKeyShort = lazy((): number => {
|
|
@@ -128,7 +138,7 @@ export interface ChangeIdentityPayload {
|
|
|
128
138
|
serverId: string;
|
|
129
139
|
mountedPort: number | undefined;
|
|
130
140
|
debugEntryPoint: string | undefined;
|
|
131
|
-
|
|
141
|
+
callerIsNode: boolean;
|
|
132
142
|
}
|
|
133
143
|
class IdentityControllerBase {
|
|
134
144
|
// IMPORTANT! We HAVE to call changeIdentity NOT JUST because we can't use peer certificates in the browser, BUT, also
|
|
@@ -146,7 +156,7 @@ class IdentityControllerBase {
|
|
|
146
156
|
throw new Error(`Signed payload too old, ${payload.time} < ${signedThreshold} from ${caller.localNodeId} (${caller.nodeId})`);
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
if (payload.
|
|
159
|
+
if (payload.callerIsNode && payload.serverId !== getOwnNodeId()) {
|
|
150
160
|
// This is extremely common when we reuse ports, which we do frequently for the edge nodes.
|
|
151
161
|
throw new Error(`You tried to contact another server. We are ${getOwnNodeId()}, you tried to contact ${payload.serverId}.`);
|
|
152
162
|
}
|
|
@@ -159,7 +169,7 @@ class IdentityControllerBase {
|
|
|
159
169
|
throw new Error(`Identity is for another server! The connection is calling us ${localNodeId}, but signature is for ${payload.serverId}`);
|
|
160
170
|
}
|
|
161
171
|
// If they're calling from the browser, then they're not going to be able to use our machine ID, etc. However, they should be calling an actual https node, so it should still be secure for them.
|
|
162
|
-
if (payload.
|
|
172
|
+
if (payload.callerIsNode) {
|
|
163
173
|
let calledMachineId = getMachineId(payload.serverId, getDomain());
|
|
164
174
|
if (calledMachineId !== "127-0-0-1" && calledMachineId !== getOwnMachineId(getDomain())) {
|
|
165
175
|
throw new Error(`Tried to call a different machine. We are ${getOwnMachineId(getDomain())}, they called ${calledMachineId}`);
|
|
@@ -177,12 +187,24 @@ class IdentityControllerBase {
|
|
|
177
187
|
// Verify we even trust the issuer/cert pair
|
|
178
188
|
validateCertificate(getDomain(), payload.cert, payload.certIssuer);
|
|
179
189
|
|
|
180
|
-
|
|
190
|
+
// NOTE: We use the common name, and not getNodeIdIP... because anything connecting will need
|
|
191
|
+
// to use HTTPS, so connecting over the IP won't work! (unless they turn off certificate validation,
|
|
192
|
+
// which we shouldn't do...)
|
|
193
|
+
let certCommonName = getCommonName(payload.cert);
|
|
194
|
+
let reconnectNodeId: string | Promise<string>;
|
|
195
|
+
let reconnectNodeIdPromiseObj: PromiseObj<string> | undefined;
|
|
181
196
|
if (payload.mountedPort) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
reconnectNodeId =
|
|
197
|
+
reconnectNodeId = getNodeId(certCommonName, payload.mountedPort);
|
|
198
|
+
} else {
|
|
199
|
+
reconnectNodeIdPromiseObj = new PromiseObj<string>();
|
|
200
|
+
reconnectNodeId = reconnectNodeIdPromiseObj.promise;
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
reconnectNodeIdPromiseObj?.reject(new Error(`Caller did not call setMountedPort within ${formatTime(MOUNT_WAIT_TIMEOUT)} of connecting. This call requires the caller to be listening.`));
|
|
203
|
+
}, MOUNT_WAIT_TIMEOUT);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!payload.callerIsNode) {
|
|
207
|
+
reconnectNodeIdPromiseObj?.reject(new Error(`The caller says they are a client, so they are never going to have a reconnect node id`));
|
|
186
208
|
}
|
|
187
209
|
|
|
188
210
|
let pubKey = getPublicIdentifier(payload.cert);
|
|
@@ -191,7 +213,9 @@ class IdentityControllerBase {
|
|
|
191
213
|
callerInfo.set(caller, {
|
|
192
214
|
cert: { certPEM: payload.cert, issuerPEM: payload.certIssuer },
|
|
193
215
|
machineId,
|
|
216
|
+
certCommonName,
|
|
194
217
|
reconnectNodeId,
|
|
218
|
+
reconnectNodeIdPromiseObj,
|
|
195
219
|
pubKey,
|
|
196
220
|
pubKeyShort: getShortNumber(pubKey),
|
|
197
221
|
});
|
|
@@ -206,13 +230,28 @@ class IdentityControllerBase {
|
|
|
206
230
|
});
|
|
207
231
|
|
|
208
232
|
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.
|
|
233
|
+
// 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
234
|
// 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
235
|
console.info(`Disconnected client`, {
|
|
212
236
|
clientId: caller.nodeId,
|
|
213
237
|
});
|
|
214
238
|
});
|
|
215
239
|
}
|
|
240
|
+
|
|
241
|
+
@measureFnc
|
|
242
|
+
public async setMountedPort(mountedPort: number) {
|
|
243
|
+
const caller = SocketFunction.getCaller();
|
|
244
|
+
let info = getCallerInfoAssert(caller);
|
|
245
|
+
if (typeof info.reconnectNodeId === "string") throw new Error(`Already have reconnectNodeId, not sure why we are being given another, Have ${info.reconnectNodeId}, given port: ${mountedPort}`);
|
|
246
|
+
let reconnectNodeId = getNodeId(info.certCommonName, mountedPort);
|
|
247
|
+
info.reconnectNodeId = reconnectNodeId;
|
|
248
|
+
info.reconnectNodeIdPromiseObj?.resolve(reconnectNodeId);
|
|
249
|
+
console.info(`Caller mounted after connecting, set reconnect node id`, {
|
|
250
|
+
clientId: caller.nodeId,
|
|
251
|
+
reconnectNodeId,
|
|
252
|
+
mountedPort,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
216
255
|
}
|
|
217
256
|
|
|
218
257
|
const IdentityController = SocketFunction.register(
|
|
@@ -227,6 +266,7 @@ const IdentityController = SocketFunction.register(
|
|
|
227
266
|
// noClientHooks: true,
|
|
228
267
|
// noDefaultHooks: true,
|
|
229
268
|
},
|
|
269
|
+
setMountedPort: {},
|
|
230
270
|
})
|
|
231
271
|
);
|
|
232
272
|
|
|
@@ -242,7 +282,7 @@ const changeIdentityOnce = cacheWeak(async function changeIdentityOnce(connectio
|
|
|
242
282
|
certIssuer: issuer.cert.toString(),
|
|
243
283
|
mountedPort: getNodeIdLocation(SocketFunction.mountedNodeId)?.port,
|
|
244
284
|
debugEntryPoint: isServer() ? process.argv[1] : "browser",
|
|
245
|
-
|
|
285
|
+
callerIsNode: isServer() && !isIpDomain(nodeId),
|
|
246
286
|
};
|
|
247
287
|
let signature = sign(threadKeyCert, payload);
|
|
248
288
|
await timeoutToError(
|
|
@@ -250,6 +290,15 @@ const changeIdentityOnce = cacheWeak(async function changeIdentityOnce(connectio
|
|
|
250
290
|
IdentityController.nodes[nodeId].changeIdentity(signature, payload),
|
|
251
291
|
() => new Error(`Timeout calling changeIdentity for ${nodeId}`)
|
|
252
292
|
);
|
|
293
|
+
if (isServer() && !payload.mountedPort) {
|
|
294
|
+
logErrors((async () => {
|
|
295
|
+
await SocketFunction.mountPromise;
|
|
296
|
+
let mountedPort = getNodeIdLocation(SocketFunction.mountedNodeId)?.port;
|
|
297
|
+
if (!mountedPort) throw new Error(`Mounted after connecting to ${nodeId}, but no mounted port found? mounted node id: ${SocketFunction.mountedNodeId}`);
|
|
298
|
+
console.info(`Mounted after connecting to ${nodeId}, sending mounted port ${mountedPort}`);
|
|
299
|
+
await IdentityController.nodes[nodeId].setMountedPort(mountedPort);
|
|
300
|
+
})());
|
|
301
|
+
}
|
|
253
302
|
});
|
|
254
303
|
let startupTime = Date.now();
|
|
255
304
|
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;
|