socket-function 0.113.0 → 0.115.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/SocketFunction.ts +464 -462
- package/package.json +3 -3
- package/src/CallFactory.ts +785 -779
- package/src/nodeCache.ts +115 -111
- package/time/trueTimeShim.ts +3 -0
package/src/nodeCache.ts
CHANGED
|
@@ -1,112 +1,116 @@
|
|
|
1
|
-
import { CallFactory, createCallFactory } from "./CallFactory";
|
|
2
|
-
import { MaybePromise } from "./types";
|
|
3
|
-
import { lazy } from "./caching";
|
|
4
|
-
import { SocketFunction } from "../SocketFunction";
|
|
5
|
-
import { isNode } from "./misc";
|
|
6
|
-
|
|
7
|
-
// TODO: Add CallInstanceFactory.isClosed, so nodeCache can clean up old entries.
|
|
8
|
-
// This is only needed for memory management, and not for correctness. Entries never
|
|
9
|
-
// need to be refreshed, because NetworkLocation.listeningPorts shouldn't really change.
|
|
10
|
-
// Either we will have listeningPorts and re-establish the connection, or we won't, and
|
|
11
|
-
// then it is a client, in which case we cannot re-establish the connection (and we just
|
|
12
|
-
// have to wait for the client to re-establish it). AND, if the listeningPorts change from
|
|
13
|
-
// a value to a new value... then they should be obtained using connect() anyway,
|
|
14
|
-
// and so whatever way the user got the NetworkLocation to begin with, they should use again.
|
|
15
|
-
|
|
16
|
-
export function getNodeId(domain: string, port: number): string {
|
|
17
|
-
// NOTE: As domains are never reused, this doesn't need any randomness
|
|
18
|
-
return `${domain}:${port}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** @deprecated, call getBrowserUrlNode instead, which does important additional checks. */
|
|
22
|
-
export function getNodeIdFromLocation() {
|
|
23
|
-
return SocketFunction.browserNodeId();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** A nodeId not available for reconnecting. */
|
|
27
|
-
export function getClientNodeId(address: string): string {
|
|
28
|
-
return `client:${address}:${Date.now()}:${Math.random()}`;
|
|
29
|
-
}
|
|
30
|
-
export function isClientNodeId(nodeId: string): boolean {
|
|
31
|
-
return nodeId.startsWith("client:");
|
|
32
|
-
}
|
|
33
|
-
/** Will always be available, even if getNodeIdLocation is not (as we don't always have the port,
|
|
34
|
-
* but we should always have an address).
|
|
35
|
-
* - Rarely used, as for logging you can just log the nodeId. ALSO, it isn't sufficient to reconnect, as the port is also needed!
|
|
36
|
-
* */
|
|
37
|
-
export function getNodeIdIP(nodeId: string): string {
|
|
38
|
-
if (isClientNodeId(nodeId)) {
|
|
39
|
-
return nodeId.split(":")[1];
|
|
40
|
-
}
|
|
41
|
-
return getNodeIdLocation(nodeId)!.address;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function getNodeIdLocation(nodeId: string): { address: string, port: number; } | undefined {
|
|
45
|
-
if (isClientNodeId(nodeId)) {
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
let [address, port] = nodeId.split(":");
|
|
49
|
-
return { address, port: parseInt(port) };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function getNodeIdDomain(nodeId: string): string {
|
|
53
|
-
let result = getNodeIdDomainMaybeUndefined(nodeId);
|
|
54
|
-
if (result === undefined) {
|
|
55
|
-
throw new Error(`Cannot get domain from nodeId, which is only usable as a client. NodeId: ${JSON.stringify(nodeId)}`);
|
|
56
|
-
}
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
|
-
export function getNodeIdDomainMaybeUndefined(nodeId: string): string | undefined {
|
|
60
|
-
let location = getNodeIdLocation(nodeId);
|
|
61
|
-
if (!location) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
return new URL(location.address).hostname.split(".").slice(-2).join(".");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// NOTE: CallFactory turns into an actual CallFactory when registerNodeClient is called
|
|
68
|
-
// nodeId =>
|
|
69
|
-
const nodeCache = new Map<string, MaybePromise<CallFactory>>();
|
|
70
|
-
|
|
71
|
-
// NOTE: Should be called directly inside call factory constructor whenever
|
|
72
|
-
// their nodeId changes (and on construction).
|
|
73
|
-
export function registerNodeClient(callFactory: CallFactory) {
|
|
74
|
-
nodeCache.set(callFactory.nodeId, callFactory);
|
|
75
|
-
startCleanupLoop();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function getCreateCallFactory(nodeId: string): MaybePromise<CallFactory> {
|
|
79
|
-
let callFactory = nodeCache.get(nodeId);
|
|
80
|
-
if (callFactory === undefined) {
|
|
81
|
-
callFactory = createCallFactory(undefined, nodeId);
|
|
82
|
-
nodeCache.set(nodeId, callFactory);
|
|
83
|
-
}
|
|
84
|
-
return callFactory;
|
|
85
|
-
}
|
|
86
|
-
export function getCallFactory(nodeId: string): MaybePromise<CallFactory | undefined> {
|
|
87
|
-
return nodeCache.get(nodeId);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function resetAllNodeCallFactories() {
|
|
91
|
-
// Needs to be done if you want to reset authentication. Outstanding calls still work,
|
|
92
|
-
// but new calls use new factories (and connections).
|
|
93
|
-
nodeCache.clear();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
import { CallFactory, createCallFactory } from "./CallFactory";
|
|
2
|
+
import { MaybePromise } from "./types";
|
|
3
|
+
import { lazy } from "./caching";
|
|
4
|
+
import { SocketFunction } from "../SocketFunction";
|
|
5
|
+
import { isNode } from "./misc";
|
|
6
|
+
|
|
7
|
+
// TODO: Add CallInstanceFactory.isClosed, so nodeCache can clean up old entries.
|
|
8
|
+
// This is only needed for memory management, and not for correctness. Entries never
|
|
9
|
+
// need to be refreshed, because NetworkLocation.listeningPorts shouldn't really change.
|
|
10
|
+
// Either we will have listeningPorts and re-establish the connection, or we won't, and
|
|
11
|
+
// then it is a client, in which case we cannot re-establish the connection (and we just
|
|
12
|
+
// have to wait for the client to re-establish it). AND, if the listeningPorts change from
|
|
13
|
+
// a value to a new value... then they should be obtained using connect() anyway,
|
|
14
|
+
// and so whatever way the user got the NetworkLocation to begin with, they should use again.
|
|
15
|
+
|
|
16
|
+
export function getNodeId(domain: string, port: number): string {
|
|
17
|
+
// NOTE: As domains are never reused, this doesn't need any randomness
|
|
18
|
+
return `${domain}:${port}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @deprecated, call getBrowserUrlNode instead, which does important additional checks. */
|
|
22
|
+
export function getNodeIdFromLocation() {
|
|
23
|
+
return SocketFunction.browserNodeId();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A nodeId not available for reconnecting. */
|
|
27
|
+
export function getClientNodeId(address: string): string {
|
|
28
|
+
return `client:${address}:${Date.now()}:${Math.random()}`;
|
|
29
|
+
}
|
|
30
|
+
export function isClientNodeId(nodeId: string): boolean {
|
|
31
|
+
return nodeId.startsWith("client:");
|
|
32
|
+
}
|
|
33
|
+
/** Will always be available, even if getNodeIdLocation is not (as we don't always have the port,
|
|
34
|
+
* but we should always have an address).
|
|
35
|
+
* - Rarely used, as for logging you can just log the nodeId. ALSO, it isn't sufficient to reconnect, as the port is also needed!
|
|
36
|
+
* */
|
|
37
|
+
export function getNodeIdIP(nodeId: string): string {
|
|
38
|
+
if (isClientNodeId(nodeId)) {
|
|
39
|
+
return nodeId.split(":")[1];
|
|
40
|
+
}
|
|
41
|
+
return getNodeIdLocation(nodeId)!.address;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getNodeIdLocation(nodeId: string): { address: string, port: number; } | undefined {
|
|
45
|
+
if (isClientNodeId(nodeId)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
let [address, port] = nodeId.split(":");
|
|
49
|
+
return { address, port: parseInt(port) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getNodeIdDomain(nodeId: string): string {
|
|
53
|
+
let result = getNodeIdDomainMaybeUndefined(nodeId);
|
|
54
|
+
if (result === undefined) {
|
|
55
|
+
throw new Error(`Cannot get domain from nodeId, which is only usable as a client. NodeId: ${JSON.stringify(nodeId)}`);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
export function getNodeIdDomainMaybeUndefined(nodeId: string): string | undefined {
|
|
60
|
+
let location = getNodeIdLocation(nodeId);
|
|
61
|
+
if (!location) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return new URL(location.address).hostname.split(".").slice(-2).join(".");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// NOTE: CallFactory turns into an actual CallFactory when registerNodeClient is called
|
|
68
|
+
// nodeId =>
|
|
69
|
+
const nodeCache = new Map<string, MaybePromise<CallFactory>>();
|
|
70
|
+
|
|
71
|
+
// NOTE: Should be called directly inside call factory constructor whenever
|
|
72
|
+
// their nodeId changes (and on construction).
|
|
73
|
+
export function registerNodeClient(callFactory: CallFactory) {
|
|
74
|
+
nodeCache.set(callFactory.nodeId, callFactory);
|
|
75
|
+
startCleanupLoop();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getCreateCallFactory(nodeId: string): MaybePromise<CallFactory> {
|
|
79
|
+
let callFactory = nodeCache.get(nodeId);
|
|
80
|
+
if (callFactory === undefined) {
|
|
81
|
+
callFactory = createCallFactory(undefined, nodeId);
|
|
82
|
+
nodeCache.set(nodeId, callFactory);
|
|
83
|
+
}
|
|
84
|
+
return callFactory;
|
|
85
|
+
}
|
|
86
|
+
export function getCallFactory(nodeId: string): MaybePromise<CallFactory | undefined> {
|
|
87
|
+
return nodeCache.get(nodeId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function resetAllNodeCallFactories() {
|
|
91
|
+
// Needs to be done if you want to reset authentication. Outstanding calls still work,
|
|
92
|
+
// but new calls use new factories (and connections).
|
|
93
|
+
nodeCache.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function countOpenConnections() {
|
|
97
|
+
return Array.from(nodeCache.values()).filter(factory => factory instanceof Promise ? false : factory.isConnected).length;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const startCleanupLoop = lazy(() => {
|
|
101
|
+
(async () => {
|
|
102
|
+
while (true) {
|
|
103
|
+
for (let [key, value] of Array.from(nodeCache.entries())) {
|
|
104
|
+
let factory = value;
|
|
105
|
+
if (!(factory instanceof Promise)) {
|
|
106
|
+
if (factory.closedForever) {
|
|
107
|
+
nodeCache.delete(key);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * 60 * 5));
|
|
112
|
+
}
|
|
113
|
+
})().catch(e => {
|
|
114
|
+
console.error(`nodeCache cleanup loop failed, ${e.stack}`);
|
|
115
|
+
});
|
|
112
116
|
});
|
package/time/trueTimeShim.ts
CHANGED
|
@@ -57,7 +57,10 @@ export function getTrueTimeOffset() {
|
|
|
57
57
|
export function waitForFirstTimeSync() {
|
|
58
58
|
return firstTimeSyncPromise;
|
|
59
59
|
}
|
|
60
|
+
let shimmed = false;
|
|
60
61
|
export function shimDateNow() {
|
|
62
|
+
if (shimmed) return;
|
|
63
|
+
shimmed = true;
|
|
61
64
|
Date.now = getTrueTime;
|
|
62
65
|
}
|
|
63
66
|
export function getBrowserTime() {
|