querysub 0.433.0 → 0.436.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/.eslintrc.js +50 -50
- package/bin/deploy.js +0 -0
- package/bin/function.js +0 -0
- package/bin/server.js +0 -0
- package/costsBenefits.txt +115 -115
- package/deploy.ts +2 -2
- package/package.json +1 -1
- package/spec.txt +1192 -1192
- package/src/-a-archives/archives.ts +202 -202
- package/src/-a-archives/archivesBackBlaze.ts +1 -0
- package/src/-a-archives/archivesDisk.ts +454 -454
- package/src/-a-auth/certs.ts +540 -540
- package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
- package/src/-b-authorities/dnsAuthority.ts +138 -138
- package/src/-c-identity/IdentityController.ts +258 -258
- package/src/-d-trust/NetworkTrust2.ts +180 -180
- package/src/-e-certs/EdgeCertController.ts +252 -252
- package/src/-e-certs/certAuthority.ts +201 -201
- package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
- package/src/-g-core-values/NodeCapabilities.ts +200 -200
- package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
- package/src/0-path-value-core/PathValueCommitter.ts +468 -468
- package/src/0-path-value-core/pathValueCore.ts +2 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
- package/src/2-proxy/TransactionDelayer.ts +94 -94
- package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
- package/src/2-proxy/pathValueProxy.ts +159 -159
- package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
- package/src/3-path-functions/pathFunctionLoader.ts +516 -516
- package/src/3-path-functions/tests/rejectTest.ts +76 -76
- package/src/4-deploy/deployCheck.ts +6 -6
- package/src/4-dom/css.tsx +29 -29
- package/src/4-dom/cssTypes.d.ts +211 -211
- package/src/4-dom/qreact.tsx +2799 -2799
- package/src/4-dom/qreactTest.tsx +410 -410
- package/src/4-querysub/permissions.ts +335 -335
- package/src/4-querysub/querysubPrediction.ts +483 -483
- package/src/5-diagnostics/qreactDebug.tsx +346 -346
- package/src/TestController.ts +34 -34
- package/src/bits.ts +104 -104
- package/src/buffers.ts +69 -69
- package/src/diagnostics/ActionsHistory.ts +57 -57
- package/src/diagnostics/listenOnDebugger.ts +71 -71
- package/src/diagnostics/periodic.ts +111 -111
- package/src/diagnostics/trackResources.ts +91 -91
- package/src/diagnostics/watchdog.ts +120 -120
- package/src/errors.ts +133 -133
- package/src/forceProduction.ts +2 -2
- package/src/fs.ts +80 -80
- package/src/functional/diff.ts +857 -857
- package/src/functional/promiseCache.ts +78 -78
- package/src/functional/random.ts +8 -8
- package/src/functional/stats.ts +60 -60
- package/src/heapDumps.ts +665 -665
- package/src/https.ts +1 -1
- package/src/library-components/AspectSizedComponent.tsx +87 -87
- package/src/library-components/ButtonSelector.tsx +64 -64
- package/src/library-components/DropdownCustom.tsx +150 -150
- package/src/library-components/DropdownSelector.tsx +31 -31
- package/src/library-components/InlinePopup.tsx +66 -66
- package/src/misc/color.ts +29 -29
- package/src/misc/hash.ts +83 -83
- package/src/misc/ipPong.js +13 -13
- package/src/misc/networking.ts +1 -1
- package/src/misc/random.ts +44 -44
- package/src/misc.ts +196 -196
- package/src/path.ts +255 -255
- package/src/persistentLocalStore.ts +41 -41
- package/src/promise.ts +14 -14
- package/src/storage/fileSystemPointer.ts +71 -71
- package/src/test/heapProcess.ts +35 -35
- package/src/zip.ts +15 -15
- package/tsconfig.json +26 -26
- package/yarnSpec.txt +56 -56
|
@@ -1,252 +1,252 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Maintains an HTTPS certificate which all nodes may use (for access of nodes in the browser)
|
|
3
|
-
- Also maintains the multi-ip A record which all nodes contribute to
|
|
4
|
-
|
|
5
|
-
IMPORTANT! HTTPS certificates have to be shared, and therefore we should (and do) only use them for the browser
|
|
6
|
-
(using SNI to determine if calls are browser or server calls).
|
|
7
|
-
- They have to be shared because letsencrypt has a limit on how many certificates you can generate per domain,
|
|
8
|
-
and our server count could easily exceed that limit (and we can't reuse certificates cross machine,
|
|
9
|
-
both for security, and for routing reasons).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { SocketFunction } from "socket-function/SocketFunction";
|
|
13
|
-
import { lazy } from "socket-function/src/caching";
|
|
14
|
-
import { createX509, generateKeyPair, getMachineId, getOwnMachineId, getThreadKeyCert, parseCert } from "../-a-auth/certs";
|
|
15
|
-
import { backoffRetryLoop, logErrors } from "../errors";
|
|
16
|
-
import { getHTTPSKeyCert } from "./certAuthority";
|
|
17
|
-
import { getControllerNodeId } from "../-g-core-values/NodeCapabilities";
|
|
18
|
-
import * as forge from "node-forge";
|
|
19
|
-
import { getNodeIdIP, getNodeIdLocation } from "socket-function/src/nodeCache";
|
|
20
|
-
import { addRecord, deleteRecord, getRecords, hasDNSWritePermissions, setRecord } from "../-b-authorities/dnsAuthority";
|
|
21
|
-
import { SocketServerConfig } from "socket-function/src/webSocketServer";
|
|
22
|
-
import debugbreak from "debugbreak";
|
|
23
|
-
import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
24
|
-
import { getDomain, isBootstrapOnly, isNoNetwork, isPublic } from "../config";
|
|
25
|
-
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
26
|
-
import { getExternalIP, testTCPIsListening } from "../misc/networking";
|
|
27
|
-
import { magenta, yellow } from "socket-function/src/formatting/logColors";
|
|
28
|
-
import { timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
29
|
-
import { nodeDiscoveryShutdown } from "../-f-node-discovery/NodeDiscovery";
|
|
30
|
-
import { shutdown } from "../diagnostics/periodic";
|
|
31
|
-
import { formatDateTime } from "socket-function/src/formatting/format";
|
|
32
|
-
|
|
33
|
-
let publicPort = -1;
|
|
34
|
-
|
|
35
|
-
export const getHostedIP = lazy(async () => {
|
|
36
|
-
if (!isPublic()) {
|
|
37
|
-
return "127.0.0.1";
|
|
38
|
-
}
|
|
39
|
-
return await getExternalIP();
|
|
40
|
-
});
|
|
41
|
-
export const getIPDomain = lazy(async () => {
|
|
42
|
-
let ip = await getHostedIP();
|
|
43
|
-
return ip.replaceAll(".", "-") + "." + getDomain();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Figure out how to hook up our watch so it actually updates the underlying server
|
|
47
|
-
export async function getSNICerts(config: {
|
|
48
|
-
publicPort: number
|
|
49
|
-
}): Promise<Exclude<SocketServerConfig["SNICerts"], undefined>> {
|
|
50
|
-
if (config.publicPort > 0) {
|
|
51
|
-
if (publicPort !== -1 && publicPort !== config.publicPort) {
|
|
52
|
-
throw new Error(`getSNICerts called with different publicPort ${publicPort} and ${config.publicPort}. We require the same port, so we can correctly maintain the A record for the edge node.`);
|
|
53
|
-
}
|
|
54
|
-
publicPort = config.publicPort;
|
|
55
|
-
}
|
|
56
|
-
let certs: Exclude<SocketServerConfig["SNICerts"], undefined> = {
|
|
57
|
-
[getDomain()]: async (callback) => {
|
|
58
|
-
await EdgeCertController_watchHTTPSKeyCert(callback);
|
|
59
|
-
},
|
|
60
|
-
[getOwnMachineId() + "." + getDomain()]: async (callback) => {
|
|
61
|
-
let threadCert = await getThreadKeyCert();
|
|
62
|
-
callback({
|
|
63
|
-
key: threadCert.key,
|
|
64
|
-
cert: threadCert.cert,
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
return certs;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const certUpdateLoop = lazy(() => {
|
|
72
|
-
logErrors((async () => {
|
|
73
|
-
let firstLoop = true;
|
|
74
|
-
while (true) {
|
|
75
|
-
let curCert = await getHTTPSKeyCert(getDomain());
|
|
76
|
-
if (!curCert) {
|
|
77
|
-
throw new Error(`Internal error, certUpdateLoop called before lastPromise was set`);
|
|
78
|
-
}
|
|
79
|
-
let certObj = parseCert(curCert.cert);
|
|
80
|
-
// Get expiration date
|
|
81
|
-
let expirationTime = +new Date(certObj.validity.notAfter);
|
|
82
|
-
let createTime = +new Date(certObj.validity.notBefore);
|
|
83
|
-
|
|
84
|
-
// If 75% of the lifetime has passed, getHTTPSKeyCert should have updated it, so wait until that, then get the new cert, and update our watchers (which should be the servers).
|
|
85
|
-
let renewDate = createTime + (expirationTime - createTime) * 0.75;
|
|
86
|
-
let timeToExpire = renewDate - Date.now();
|
|
87
|
-
|
|
88
|
-
if (!firstLoop) {
|
|
89
|
-
for (let callback of callbacks) {
|
|
90
|
-
callback(curCert);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
firstLoop = false;
|
|
94
|
-
|
|
95
|
-
if (timeToExpire < 0) {
|
|
96
|
-
console.warn(`getHTTPSKeyCert gave as an almost expired. It is not supposed to do this. It should have updated it by now... Expires on ${formatDateTime(expirationTime)}`);
|
|
97
|
-
timeToExpire = timeInMinute * 15;
|
|
98
|
-
}
|
|
99
|
-
// Max timeout a signed integer, but lower is fine too
|
|
100
|
-
timeToExpire = Math.min(Math.floor(timeToExpire), 2 ** 30);
|
|
101
|
-
await delay(timeToExpire);
|
|
102
|
-
console.log(`Woke up to propagate new certs`);
|
|
103
|
-
}
|
|
104
|
-
})());
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
let callbacks: ((newCertPair: { cert: string; key: string }) => void)[] = [];
|
|
110
|
-
async function EdgeCertController_watchHTTPSKeyCert(
|
|
111
|
-
callback: (newCertPair: { cert: string; key: string }) => void
|
|
112
|
-
) {
|
|
113
|
-
certUpdateLoop();
|
|
114
|
-
|
|
115
|
-
callbacks.push(callback);
|
|
116
|
-
let certPair = await getHTTPSKeyCert(getDomain());
|
|
117
|
-
callback(certPair);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** NOTE: Any SocketFunction.mount calls should probably call this, otherwise they won't be able to be called. */
|
|
121
|
-
export async function publishMachineARecords() {
|
|
122
|
-
if (!SocketFunction.isMounted()) {
|
|
123
|
-
throw new Error(`Must mount before publishing machine A records`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let ip = await getHostedIP();
|
|
127
|
-
|
|
128
|
-
let selfNodeId = SocketFunction.mountedNodeId;
|
|
129
|
-
let nodeObj = getNodeIdLocation(selfNodeId);
|
|
130
|
-
if (!nodeObj) throw new Error(`Invalid nodeId ${selfNodeId}`);
|
|
131
|
-
let machineAddress = nodeObj.address.split(".").slice(1).join(".");
|
|
132
|
-
let prevMachineIP = await getRecords("A", machineAddress);
|
|
133
|
-
let ipDomain = await getIPDomain();
|
|
134
|
-
let promises: Promise<void>[] = [];
|
|
135
|
-
promises.push(setRecord("A", ipDomain, ip));
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (ip === "127.0.0.1" && prevMachineIP.length > 0 && !prevMachineIP.includes("127.0.0.1")) {
|
|
139
|
-
console.log(yellow(`Not setting A record for ${machineAddress} to ${ip}, as we previously had a public IP. IF you want to switch back to 127.0.0.1, manually go in and delete the A records for ${machineAddress}. Port forwarding should allow this to work anyways, and the bootstrapper should be smart enough to try 127-0-0-1 style addresses to allow fast development (ex, if you want to download a large file from the local development server quickly).`));
|
|
140
|
-
} else {
|
|
141
|
-
promises.push(setRecord("A", machineAddress, ip));
|
|
142
|
-
promises.push(setRecord("A", "*." + machineAddress, ip));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
promises.push(publishEdgeDomain());
|
|
146
|
-
await Promise.all(promises);
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
ip,
|
|
150
|
-
ipDomain,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const runEdgeDomainAliveLoop = lazy(() => {
|
|
155
|
-
// NOTE: Our DNS TTL is 1 minute, which means no matter how fast we poll,
|
|
156
|
-
// we can't get below that. Of course the worst case is that + our poll rate,
|
|
157
|
-
// but still, this means there is less and less benefit the lower this value is.
|
|
158
|
-
runInfinitePoll(timeInMinute * 3, checkEdgeDomainsAlive);
|
|
159
|
-
});
|
|
160
|
-
async function checkEdgeDomainsAlive() {
|
|
161
|
-
if (isNoNetwork()) return;
|
|
162
|
-
if (publicPort === -1) return;
|
|
163
|
-
// Only check once we are actually running on real sites (not just developer sites, which
|
|
164
|
-
// use non-port 443)
|
|
165
|
-
if (publicPort !== 443) return;
|
|
166
|
-
const edgeDomain = getDomain();
|
|
167
|
-
let ips = await getRecords("A", edgeDomain);
|
|
168
|
-
// Don't check 127.0.0.1, if we are in development this will still be set,
|
|
169
|
-
// and it will be removed when we leave development
|
|
170
|
-
ips = ips.filter(ip => ip !== "127.0.0.1");
|
|
171
|
-
|
|
172
|
-
let results = await Promise.all(ips.map(async ip => {
|
|
173
|
-
// It must be not listening for 3 times in a row
|
|
174
|
-
for (let i = 0; i < 3; i++) {
|
|
175
|
-
let isListening = await testTCPIsListening(ip, publicPort);
|
|
176
|
-
if (isListening) return true;
|
|
177
|
-
let areWeOnline = await testTCPIsListening("1.1.1.1", publicPort);
|
|
178
|
-
if (!areWeOnline) {
|
|
179
|
-
// NOTE: I mean, while this case can happen, it also won't matter because we can't really delete the A record if we're not on the internet. I guess it's useful if we're offline when we check the IPs, but then we come back online right before we delete the records. But I doubt that will happen.t
|
|
180
|
-
console.warn(`Ignoring down ip, as we cannot even connect to 1.1.1.1:${publicPort}, so the other node isn't down, we're down!`);
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
console.warn(`IP ${ip}:${publicPort} is not listening, waiting 10 seconds before retrying. If it fails 3 times in a row, the A record will be removed.`);
|
|
184
|
-
await delay(timeInSecond * 10);
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
}));
|
|
188
|
-
|
|
189
|
-
let deadIPs = ips.filter((ip, i) => !results[i]);
|
|
190
|
-
if (deadIPs.length > 0) {
|
|
191
|
-
console.error(`Found dead IPs for ${edgeDomain}:${publicPort}, removing their A records: ${JSON.stringify(deadIPs)}`);
|
|
192
|
-
}
|
|
193
|
-
for (let deadIP of deadIPs) {
|
|
194
|
-
await deleteRecord("A", edgeDomain, deadIP);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async function publishEdgeDomain() {
|
|
199
|
-
let callerIP = SocketFunction.mountedIP;
|
|
200
|
-
if (callerIP === "0.0.0.0") {
|
|
201
|
-
callerIP = await getExternalIP();
|
|
202
|
-
}
|
|
203
|
-
if (!callerIP) {
|
|
204
|
-
console.warn(`publishEdgeDomain called before SocketFunction.mount was called, so we don't know the public ip.`);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
// IMPORTANT! We have to set our A record AFTER we create our cert, otherwise we might wait a while
|
|
208
|
-
// with our A record public while we create our cert.
|
|
209
|
-
runEdgeDomainAliveLoop();
|
|
210
|
-
const edgeDomain = getDomain();
|
|
211
|
-
console.log(`Starting publish check for A record for ${edgeDomain} to ${callerIP}`);
|
|
212
|
-
try {
|
|
213
|
-
let promises: Promise<void>[] = [];
|
|
214
|
-
let existingIPs = await getRecords("A", edgeDomain);
|
|
215
|
-
if (!isPublic()) {
|
|
216
|
-
if (existingIPs.length === 0) {
|
|
217
|
-
promises.push(addRecord("A", edgeDomain, callerIP));
|
|
218
|
-
} else if (existingIPs.length === 1 && existingIPs[0] === "127.0.0.1") {
|
|
219
|
-
// Good, do nothing
|
|
220
|
-
} else {
|
|
221
|
-
// Don't remove the public servers, this is probably just us accidentally running a
|
|
222
|
-
// test script for a public domain.
|
|
223
|
-
/*
|
|
224
|
-
// Maybe they took down all the public servers?
|
|
225
|
-
await removeDeadARecords();
|
|
226
|
-
existingIPs = await getRecords("A", edgeDomain);
|
|
227
|
-
if (existingIPs.length === 0) {
|
|
228
|
-
await addRecord("A", edgeDomain, callerIP);
|
|
229
|
-
} else {
|
|
230
|
-
await addRecord("A", "127-0-0-1." + edgeDomain, "127.0.0.1");
|
|
231
|
-
console.warn(`Tried to serve an edge node on the local domain, but SocketFunction.mount did NOT specify a public ip (ex, { ip: "0.0.0.0" }) AND there are already existing public servers. You can't load balance between real ips and 127.0.0.1! ${existingIPs.join(", ")}. You will need to use 127-0-0-1.${edgeDomain} to access the local server (instead of just ${edgeDomain}).`);
|
|
232
|
-
}
|
|
233
|
-
*/
|
|
234
|
-
console.log(yellow(`Current process is not marked as public, but machine previous had public services. NOT setting ${edgeDomain} to 127.0.0.1, as this would make the public services inaccessible. Assuming the current process is for development, I recommend using 127-0-0-1.${edgeDomain} or 127-0-0-1.${edgeDomain} to access the server.`));
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
if (existingIPs.includes("127.0.0.1")) {
|
|
238
|
-
console.log(magenta(`Switching from local development to production development (removing A record for 127.0.0.1, and using a real ip)`));
|
|
239
|
-
promises.push(deleteRecord("A", edgeDomain, "127.0.0.1"));
|
|
240
|
-
}
|
|
241
|
-
if (isBootstrapOnly()) {
|
|
242
|
-
promises.push(addRecord("A", edgeDomain, callerIP, "proxied"));
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
promises.push(addRecord("A", "127-0-0-1." + edgeDomain, "127.0.0.1"));
|
|
246
|
-
// Add records in parallel, so we can wait for DNS propagation in parallel
|
|
247
|
-
await Promise.all(promises);
|
|
248
|
-
} catch (e) {
|
|
249
|
-
console.error(`Error updating DNS records, continuing without updating them`, e);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
1
|
+
/*
|
|
2
|
+
Maintains an HTTPS certificate which all nodes may use (for access of nodes in the browser)
|
|
3
|
+
- Also maintains the multi-ip A record which all nodes contribute to
|
|
4
|
+
|
|
5
|
+
IMPORTANT! HTTPS certificates have to be shared, and therefore we should (and do) only use them for the browser
|
|
6
|
+
(using SNI to determine if calls are browser or server calls).
|
|
7
|
+
- They have to be shared because letsencrypt has a limit on how many certificates you can generate per domain,
|
|
8
|
+
and our server count could easily exceed that limit (and we can't reuse certificates cross machine,
|
|
9
|
+
both for security, and for routing reasons).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
13
|
+
import { lazy } from "socket-function/src/caching";
|
|
14
|
+
import { createX509, generateKeyPair, getMachineId, getOwnMachineId, getThreadKeyCert, parseCert } from "../-a-auth/certs";
|
|
15
|
+
import { backoffRetryLoop, logErrors } from "../errors";
|
|
16
|
+
import { getHTTPSKeyCert } from "./certAuthority";
|
|
17
|
+
import { getControllerNodeId } from "../-g-core-values/NodeCapabilities";
|
|
18
|
+
import * as forge from "node-forge";
|
|
19
|
+
import { getNodeIdIP, getNodeIdLocation } from "socket-function/src/nodeCache";
|
|
20
|
+
import { addRecord, deleteRecord, getRecords, hasDNSWritePermissions, setRecord } from "../-b-authorities/dnsAuthority";
|
|
21
|
+
import { SocketServerConfig } from "socket-function/src/webSocketServer";
|
|
22
|
+
import debugbreak from "debugbreak";
|
|
23
|
+
import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
24
|
+
import { getDomain, isBootstrapOnly, isNoNetwork, isPublic } from "../config";
|
|
25
|
+
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
26
|
+
import { getExternalIP, testTCPIsListening } from "../misc/networking";
|
|
27
|
+
import { magenta, yellow } from "socket-function/src/formatting/logColors";
|
|
28
|
+
import { timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
29
|
+
import { nodeDiscoveryShutdown } from "../-f-node-discovery/NodeDiscovery";
|
|
30
|
+
import { shutdown } from "../diagnostics/periodic";
|
|
31
|
+
import { formatDateTime } from "socket-function/src/formatting/format";
|
|
32
|
+
|
|
33
|
+
let publicPort = -1;
|
|
34
|
+
|
|
35
|
+
export const getHostedIP = lazy(async () => {
|
|
36
|
+
if (!isPublic()) {
|
|
37
|
+
return "127.0.0.1";
|
|
38
|
+
}
|
|
39
|
+
return await getExternalIP();
|
|
40
|
+
});
|
|
41
|
+
export const getIPDomain = lazy(async () => {
|
|
42
|
+
let ip = await getHostedIP();
|
|
43
|
+
return ip.replaceAll(".", "-") + "." + getDomain();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Figure out how to hook up our watch so it actually updates the underlying server
|
|
47
|
+
export async function getSNICerts(config: {
|
|
48
|
+
publicPort: number
|
|
49
|
+
}): Promise<Exclude<SocketServerConfig["SNICerts"], undefined>> {
|
|
50
|
+
if (config.publicPort > 0) {
|
|
51
|
+
if (publicPort !== -1 && publicPort !== config.publicPort) {
|
|
52
|
+
throw new Error(`getSNICerts called with different publicPort ${publicPort} and ${config.publicPort}. We require the same port, so we can correctly maintain the A record for the edge node.`);
|
|
53
|
+
}
|
|
54
|
+
publicPort = config.publicPort;
|
|
55
|
+
}
|
|
56
|
+
let certs: Exclude<SocketServerConfig["SNICerts"], undefined> = {
|
|
57
|
+
[getDomain()]: async (callback) => {
|
|
58
|
+
await EdgeCertController_watchHTTPSKeyCert(callback);
|
|
59
|
+
},
|
|
60
|
+
[getOwnMachineId() + "." + getDomain()]: async (callback) => {
|
|
61
|
+
let threadCert = await getThreadKeyCert();
|
|
62
|
+
callback({
|
|
63
|
+
key: threadCert.key,
|
|
64
|
+
cert: threadCert.cert,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
return certs;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const certUpdateLoop = lazy(() => {
|
|
72
|
+
logErrors((async () => {
|
|
73
|
+
let firstLoop = true;
|
|
74
|
+
while (true) {
|
|
75
|
+
let curCert = await getHTTPSKeyCert(getDomain());
|
|
76
|
+
if (!curCert) {
|
|
77
|
+
throw new Error(`Internal error, certUpdateLoop called before lastPromise was set`);
|
|
78
|
+
}
|
|
79
|
+
let certObj = parseCert(curCert.cert);
|
|
80
|
+
// Get expiration date
|
|
81
|
+
let expirationTime = +new Date(certObj.validity.notAfter);
|
|
82
|
+
let createTime = +new Date(certObj.validity.notBefore);
|
|
83
|
+
|
|
84
|
+
// If 75% of the lifetime has passed, getHTTPSKeyCert should have updated it, so wait until that, then get the new cert, and update our watchers (which should be the servers).
|
|
85
|
+
let renewDate = createTime + (expirationTime - createTime) * 0.75;
|
|
86
|
+
let timeToExpire = renewDate - Date.now();
|
|
87
|
+
|
|
88
|
+
if (!firstLoop) {
|
|
89
|
+
for (let callback of callbacks) {
|
|
90
|
+
callback(curCert);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
firstLoop = false;
|
|
94
|
+
|
|
95
|
+
if (timeToExpire < 0) {
|
|
96
|
+
console.warn(`getHTTPSKeyCert gave as an almost expired. It is not supposed to do this. It should have updated it by now... Expires on ${formatDateTime(expirationTime)}`);
|
|
97
|
+
timeToExpire = timeInMinute * 15;
|
|
98
|
+
}
|
|
99
|
+
// Max timeout a signed integer, but lower is fine too
|
|
100
|
+
timeToExpire = Math.min(Math.floor(timeToExpire), 2 ** 30);
|
|
101
|
+
await delay(timeToExpire);
|
|
102
|
+
console.log(`Woke up to propagate new certs`);
|
|
103
|
+
}
|
|
104
|
+
})());
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
let callbacks: ((newCertPair: { cert: string; key: string }) => void)[] = [];
|
|
110
|
+
async function EdgeCertController_watchHTTPSKeyCert(
|
|
111
|
+
callback: (newCertPair: { cert: string; key: string }) => void
|
|
112
|
+
) {
|
|
113
|
+
certUpdateLoop();
|
|
114
|
+
|
|
115
|
+
callbacks.push(callback);
|
|
116
|
+
let certPair = await getHTTPSKeyCert(getDomain());
|
|
117
|
+
callback(certPair);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** NOTE: Any SocketFunction.mount calls should probably call this, otherwise they won't be able to be called. */
|
|
121
|
+
export async function publishMachineARecords() {
|
|
122
|
+
if (!SocketFunction.isMounted()) {
|
|
123
|
+
throw new Error(`Must mount before publishing machine A records`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let ip = await getHostedIP();
|
|
127
|
+
|
|
128
|
+
let selfNodeId = SocketFunction.mountedNodeId;
|
|
129
|
+
let nodeObj = getNodeIdLocation(selfNodeId);
|
|
130
|
+
if (!nodeObj) throw new Error(`Invalid nodeId ${selfNodeId}`);
|
|
131
|
+
let machineAddress = nodeObj.address.split(".").slice(1).join(".");
|
|
132
|
+
let prevMachineIP = await getRecords("A", machineAddress);
|
|
133
|
+
let ipDomain = await getIPDomain();
|
|
134
|
+
let promises: Promise<void>[] = [];
|
|
135
|
+
promises.push(setRecord("A", ipDomain, ip));
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if (ip === "127.0.0.1" && prevMachineIP.length > 0 && !prevMachineIP.includes("127.0.0.1")) {
|
|
139
|
+
console.log(yellow(`Not setting A record for ${machineAddress} to ${ip}, as we previously had a public IP. IF you want to switch back to 127.0.0.1, manually go in and delete the A records for ${machineAddress}. Port forwarding should allow this to work anyways, and the bootstrapper should be smart enough to try 127-0-0-1 style addresses to allow fast development (ex, if you want to download a large file from the local development server quickly).`));
|
|
140
|
+
} else {
|
|
141
|
+
promises.push(setRecord("A", machineAddress, ip));
|
|
142
|
+
promises.push(setRecord("A", "*." + machineAddress, ip));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
promises.push(publishEdgeDomain());
|
|
146
|
+
await Promise.all(promises);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
ip,
|
|
150
|
+
ipDomain,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const runEdgeDomainAliveLoop = lazy(() => {
|
|
155
|
+
// NOTE: Our DNS TTL is 1 minute, which means no matter how fast we poll,
|
|
156
|
+
// we can't get below that. Of course the worst case is that + our poll rate,
|
|
157
|
+
// but still, this means there is less and less benefit the lower this value is.
|
|
158
|
+
runInfinitePoll(timeInMinute * 3, checkEdgeDomainsAlive);
|
|
159
|
+
});
|
|
160
|
+
async function checkEdgeDomainsAlive() {
|
|
161
|
+
if (isNoNetwork()) return;
|
|
162
|
+
if (publicPort === -1) return;
|
|
163
|
+
// Only check once we are actually running on real sites (not just developer sites, which
|
|
164
|
+
// use non-port 443)
|
|
165
|
+
if (publicPort !== 443) return;
|
|
166
|
+
const edgeDomain = getDomain();
|
|
167
|
+
let ips = await getRecords("A", edgeDomain);
|
|
168
|
+
// Don't check 127.0.0.1, if we are in development this will still be set,
|
|
169
|
+
// and it will be removed when we leave development
|
|
170
|
+
ips = ips.filter(ip => ip !== "127.0.0.1");
|
|
171
|
+
|
|
172
|
+
let results = await Promise.all(ips.map(async ip => {
|
|
173
|
+
// It must be not listening for 3 times in a row
|
|
174
|
+
for (let i = 0; i < 3; i++) {
|
|
175
|
+
let isListening = await testTCPIsListening(ip, publicPort);
|
|
176
|
+
if (isListening) return true;
|
|
177
|
+
let areWeOnline = await testTCPIsListening("1.1.1.1", publicPort);
|
|
178
|
+
if (!areWeOnline) {
|
|
179
|
+
// NOTE: I mean, while this case can happen, it also won't matter because we can't really delete the A record if we're not on the internet. I guess it's useful if we're offline when we check the IPs, but then we come back online right before we delete the records. But I doubt that will happen.t
|
|
180
|
+
console.warn(`Ignoring down ip, as we cannot even connect to 1.1.1.1:${publicPort}, so the other node isn't down, we're down!`);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
console.warn(`IP ${ip}:${publicPort} is not listening, waiting 10 seconds before retrying. If it fails 3 times in a row, the A record will be removed.`);
|
|
184
|
+
await delay(timeInSecond * 10);
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
let deadIPs = ips.filter((ip, i) => !results[i]);
|
|
190
|
+
if (deadIPs.length > 0) {
|
|
191
|
+
console.error(`Found dead IPs for ${edgeDomain}:${publicPort}, removing their A records: ${JSON.stringify(deadIPs)}`);
|
|
192
|
+
}
|
|
193
|
+
for (let deadIP of deadIPs) {
|
|
194
|
+
await deleteRecord("A", edgeDomain, deadIP);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function publishEdgeDomain() {
|
|
199
|
+
let callerIP = SocketFunction.mountedIP;
|
|
200
|
+
if (callerIP === "0.0.0.0") {
|
|
201
|
+
callerIP = await getExternalIP();
|
|
202
|
+
}
|
|
203
|
+
if (!callerIP) {
|
|
204
|
+
console.warn(`publishEdgeDomain called before SocketFunction.mount was called, so we don't know the public ip.`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// IMPORTANT! We have to set our A record AFTER we create our cert, otherwise we might wait a while
|
|
208
|
+
// with our A record public while we create our cert.
|
|
209
|
+
runEdgeDomainAliveLoop();
|
|
210
|
+
const edgeDomain = getDomain();
|
|
211
|
+
console.log(`Starting publish check for A record for ${edgeDomain} to ${callerIP}`);
|
|
212
|
+
try {
|
|
213
|
+
let promises: Promise<void>[] = [];
|
|
214
|
+
let existingIPs = await getRecords("A", edgeDomain);
|
|
215
|
+
if (!isPublic()) {
|
|
216
|
+
if (existingIPs.length === 0) {
|
|
217
|
+
promises.push(addRecord("A", edgeDomain, callerIP));
|
|
218
|
+
} else if (existingIPs.length === 1 && existingIPs[0] === "127.0.0.1") {
|
|
219
|
+
// Good, do nothing
|
|
220
|
+
} else {
|
|
221
|
+
// Don't remove the public servers, this is probably just us accidentally running a
|
|
222
|
+
// test script for a public domain.
|
|
223
|
+
/*
|
|
224
|
+
// Maybe they took down all the public servers?
|
|
225
|
+
await removeDeadARecords();
|
|
226
|
+
existingIPs = await getRecords("A", edgeDomain);
|
|
227
|
+
if (existingIPs.length === 0) {
|
|
228
|
+
await addRecord("A", edgeDomain, callerIP);
|
|
229
|
+
} else {
|
|
230
|
+
await addRecord("A", "127-0-0-1." + edgeDomain, "127.0.0.1");
|
|
231
|
+
console.warn(`Tried to serve an edge node on the local domain, but SocketFunction.mount did NOT specify a public ip (ex, { ip: "0.0.0.0" }) AND there are already existing public servers. You can't load balance between real ips and 127.0.0.1! ${existingIPs.join(", ")}. You will need to use 127-0-0-1.${edgeDomain} to access the local server (instead of just ${edgeDomain}).`);
|
|
232
|
+
}
|
|
233
|
+
*/
|
|
234
|
+
console.log(yellow(`Current process is not marked as public, but machine previous had public services. NOT setting ${edgeDomain} to 127.0.0.1, as this would make the public services inaccessible. Assuming the current process is for development, I recommend using 127-0-0-1.${edgeDomain} or 127-0-0-1.${edgeDomain} to access the server.`));
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
if (existingIPs.includes("127.0.0.1")) {
|
|
238
|
+
console.log(magenta(`Switching from local development to production development (removing A record for 127.0.0.1, and using a real ip)`));
|
|
239
|
+
promises.push(deleteRecord("A", edgeDomain, "127.0.0.1"));
|
|
240
|
+
}
|
|
241
|
+
if (isBootstrapOnly()) {
|
|
242
|
+
promises.push(addRecord("A", edgeDomain, callerIP, "proxied"));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
promises.push(addRecord("A", "127-0-0-1." + edgeDomain, "127.0.0.1"));
|
|
246
|
+
// Add records in parallel, so we can wait for DNS propagation in parallel
|
|
247
|
+
await Promise.all(promises);
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.error(`Error updating DNS records, continuing without updating them`, e);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|