@undefineds.co/xpod 0.3.53 → 0.3.54
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/config/cli.json +72 -0
- package/config/components-ignore.json +21 -0
- package/config/extensions.local.initializer.json +77 -8
- package/config/resolver.json +72 -4
- package/dist/api/ApiServer.d.ts +12 -0
- package/dist/api/ApiServer.js +14 -3
- package/dist/api/ApiServer.js.map +1 -1
- package/dist/api/auth/NodeTokenAuthenticator.d.ts +0 -8
- package/dist/api/auth/NodeTokenAuthenticator.js +3 -46
- package/dist/api/auth/NodeTokenAuthenticator.js.map +1 -1
- package/dist/api/container/local.js +5 -36
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +6 -0
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.js +25 -3
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -1
- package/dist/api/handlers/ReachabilityHandler.d.ts +13 -0
- package/dist/api/handlers/ReachabilityHandler.js +388 -0
- package/dist/api/handlers/ReachabilityHandler.js.map +1 -0
- package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
- package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
- package/dist/components/components.jsonld +14 -2
- package/dist/components/context.jsonld +439 -3
- package/dist/edge/EdgeNodeAgent.d.ts +43 -0
- package/dist/edge/EdgeNodeAgent.js +208 -1
- package/dist/edge/EdgeNodeAgent.js.map +1 -1
- package/dist/edge/EdgeNodeAgent.jsonld +166 -0
- package/dist/edge/EdgeNodeAgentInitializer.d.ts +20 -2
- package/dist/edge/EdgeNodeAgentInitializer.js +53 -2
- package/dist/edge/EdgeNodeAgentInitializer.js.map +1 -1
- package/dist/edge/EdgeNodeAgentInitializer.jsonld +409 -0
- package/dist/edge/reachability/CanonicalFetch.d.ts +7 -0
- package/dist/edge/reachability/CanonicalFetch.js +49 -0
- package/dist/edge/reachability/CanonicalFetch.js.map +1 -0
- package/dist/edge/reachability/CanonicalFetch.jsonld +25 -0
- package/dist/edge/reachability/ManagedClientFetch.d.ts +26 -0
- package/dist/edge/reachability/ManagedClientFetch.js +155 -0
- package/dist/edge/reachability/ManagedClientFetch.js.map +1 -0
- package/dist/edge/reachability/ManagedClientFetch.jsonld +83 -0
- package/dist/edge/reachability/ManagedClientP2PSmoke.d.ts +16 -0
- package/dist/edge/reachability/ManagedClientP2PSmoke.js +31 -0
- package/dist/edge/reachability/ManagedClientP2PSmoke.js.map +1 -0
- package/dist/edge/reachability/ManagedClientP2PSmoke.jsonld +65 -0
- package/dist/edge/reachability/ManagedRouteSelector.d.ts +7 -0
- package/dist/edge/reachability/ManagedRouteSelector.js +43 -0
- package/dist/edge/reachability/ManagedRouteSelector.js.map +1 -0
- package/dist/edge/reachability/ManagedRouteSelector.jsonld +29 -0
- package/dist/edge/reachability/P2PDataPlane.d.ts +37 -0
- package/dist/edge/reachability/P2PDataPlane.js +160 -0
- package/dist/edge/reachability/P2PDataPlane.js.map +1 -0
- package/dist/edge/reachability/P2PDataPlane.jsonld +134 -0
- package/dist/edge/reachability/P2PRealnetAcceptance.d.ts +74 -0
- package/dist/edge/reachability/P2PRealnetAcceptance.js +240 -0
- package/dist/edge/reachability/P2PRealnetAcceptance.js.map +1 -0
- package/dist/edge/reachability/P2PRealnetAcceptance.jsonld +283 -0
- package/dist/edge/reachability/P2PSignalingClient.d.ts +24 -0
- package/dist/edge/reachability/P2PSignalingClient.js +138 -0
- package/dist/edge/reachability/P2PSignalingClient.js.map +1 -0
- package/dist/edge/reachability/P2PSignalingClient.jsonld +79 -0
- package/dist/edge/reachability/ReachabilitySessionService.d.ts +55 -0
- package/dist/edge/reachability/ReachabilitySessionService.js +439 -0
- package/dist/edge/reachability/ReachabilitySessionService.js.map +1 -0
- package/dist/edge/reachability/ReachabilitySessionService.jsonld +196 -0
- package/dist/edge/reachability/RouteSetBuilder.d.ts +2 -0
- package/dist/edge/reachability/RouteSetBuilder.js +205 -0
- package/dist/edge/reachability/RouteSetBuilder.js.map +1 -0
- package/dist/edge/reachability/TcpP2PDataPlaneTransport.d.ts +47 -0
- package/dist/edge/reachability/TcpP2PDataPlaneTransport.js +281 -0
- package/dist/edge/reachability/TcpP2PDataPlaneTransport.js.map +1 -0
- package/dist/edge/reachability/TcpP2PDataPlaneTransport.jsonld +183 -0
- package/dist/edge/reachability/TcpP2PSignalingSession.d.ts +149 -0
- package/dist/edge/reachability/TcpP2PSignalingSession.js +699 -0
- package/dist/edge/reachability/TcpP2PSignalingSession.js.map +1 -0
- package/dist/edge/reachability/TcpP2PSignalingSession.jsonld +474 -0
- package/dist/edge/reachability/index.d.ts +12 -0
- package/dist/edge/reachability/index.js +29 -0
- package/dist/edge/reachability/index.js.map +1 -0
- package/dist/edge/reachability/types.d.ts +114 -0
- package/dist/edge/reachability/types.js +3 -0
- package/dist/edge/reachability/types.js.map +1 -0
- package/dist/edge/reachability/types.jsonld +457 -0
- package/dist/http/EdgeNodeProxyHttpHandler.d.ts +2 -0
- package/dist/http/EdgeNodeProxyHttpHandler.js +19 -1
- package/dist/http/EdgeNodeProxyHttpHandler.js.map +1 -1
- package/dist/http/EdgeNodeProxyHttpHandler.jsonld +8 -0
- package/dist/identity/drizzle/EdgeNodeRepository.js +1 -1
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/bootstrap.js +8 -0
- package/dist/runtime/bootstrap.js.map +1 -1
- package/dist/service/EdgeNodeSignalClient.js +5 -1
- package/dist/service/EdgeNodeSignalClient.js.map +1 -1
- package/dist/storage/rdf/PostgresRdfEngine.d.ts +1 -0
- package/dist/storage/rdf/PostgresRdfEngine.js +53 -37
- package/dist/storage/rdf/PostgresRdfEngine.js.map +1 -1
- package/dist/storage/rdf/PostgresRdfEngine.jsonld +4 -0
- package/dist/test-utils/index.d.ts +2 -0
- package/dist/test-utils/index.js +3 -1
- package/dist/test-utils/index.js.map +1 -1
- package/dist/test-utils/local-managed-client-p2p-e2e-smoke.d.ts +63 -0
- package/dist/test-utils/local-managed-client-p2p-e2e-smoke.js +478 -0
- package/dist/test-utils/local-managed-client-p2p-e2e-smoke.js.map +1 -0
- package/package.json +11 -4
- package/static/app/assets/_commonjsHelpers-B-UnjaXt.js +1 -0
- package/static/app/assets/index-AaQ1qxhy.js +171 -0
- package/static/app/assets/inrupt-smoke.js +131 -0
- package/static/app/assets/main.css +1 -0
- package/static/app/assets/main.js +6 -6
- package/static/app/index.html +2 -1
- package/static/app/inrupt-smoke.html +14 -0
- package/static/app/reachability.html +221 -0
- package/static/app/reachability.svg +7 -0
- package/static/app/reachability.webmanifest +18 -0
- package/static/app/signal-pod.html +293 -0
- package/static/app/assets/index.css +0 -1
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RAW_TCP_HOLE_PUNCH_CAPABILITY = exports.RAW_TCP_HOLE_PUNCH_TRANSPORT = void 0;
|
|
4
|
+
exports.createNodeRawTcpP2PConnectSocket = createNodeRawTcpP2PConnectSocket;
|
|
5
|
+
exports.createRawTcpHolePunchCandidates = createRawTcpHolePunchCandidates;
|
|
6
|
+
exports.createSignaledRawTcpP2PSession = createSignaledRawTcpP2PSession;
|
|
7
|
+
exports.answerPendingRawTcpP2PSessionsOnce = answerPendingRawTcpP2PSessionsOnce;
|
|
8
|
+
exports.acceptSignaledRawTcpP2PConnectionOnce = acceptSignaledRawTcpP2PConnectionOnce;
|
|
9
|
+
exports.waitForRawTcpRemoteCandidates = waitForRawTcpRemoteCandidates;
|
|
10
|
+
exports.connectSignaledRawTcpP2PTransport = connectSignaledRawTcpP2PTransport;
|
|
11
|
+
exports.connectRawTcpP2PTransport = connectRawTcpP2PTransport;
|
|
12
|
+
exports.filterRawTcpRemoteCandidates = filterRawTcpRemoteCandidates;
|
|
13
|
+
exports.selectRawTcpP2PRoute = selectRawTcpP2PRoute;
|
|
14
|
+
exports.isRawTcpHolePunchCandidate = isRawTcpHolePunchCandidate;
|
|
15
|
+
const node_net_1 = require("node:net");
|
|
16
|
+
const TcpP2PDataPlaneTransport_1 = require("./TcpP2PDataPlaneTransport");
|
|
17
|
+
exports.RAW_TCP_HOLE_PUNCH_TRANSPORT = 'raw-tcp-hole-punch';
|
|
18
|
+
exports.RAW_TCP_HOLE_PUNCH_CAPABILITY = 'tcp-punch';
|
|
19
|
+
const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
20
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 10_000;
|
|
21
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 5_000;
|
|
22
|
+
const DEFAULT_RAW_TCP_RETRY_INTERVAL_MS = 25;
|
|
23
|
+
function createNodeRawTcpP2PConnectSocket(options = {}) {
|
|
24
|
+
return (attempt) => connectNodeRawTcpSocket(attempt, options);
|
|
25
|
+
}
|
|
26
|
+
const defaultNodeRawTcpP2PConnectSocket = createNodeRawTcpP2PConnectSocket();
|
|
27
|
+
function createRawTcpHolePunchCandidates(options) {
|
|
28
|
+
const plan = options.plan ?? (0, TcpP2PDataPlaneTransport_1.computeTcpHolePunchPlan)(options.planOptions);
|
|
29
|
+
const createdAt = (options.createdAt ?? new Date()).toISOString();
|
|
30
|
+
const hostOrAddress = options.host ?? options.address;
|
|
31
|
+
const prefix = options.candidateIdPrefix ?? `${options.sourceId}_${plan.bucket}`;
|
|
32
|
+
return plan.ports.map((port, index) => ({
|
|
33
|
+
id: `${prefix}_${port}_${index}`,
|
|
34
|
+
role: options.role,
|
|
35
|
+
sourceId: options.sourceId,
|
|
36
|
+
createdAt,
|
|
37
|
+
protocol: 'tcp',
|
|
38
|
+
transport: exports.RAW_TCP_HOLE_PUNCH_TRANSPORT,
|
|
39
|
+
...(options.host ? { host: options.host } : {}),
|
|
40
|
+
...(options.address ? { address: options.address } : {}),
|
|
41
|
+
...(hostOrAddress ? { url: `tcp-punch://${hostOrAddress}:${port}` } : {}),
|
|
42
|
+
port,
|
|
43
|
+
priority: options.priority ?? (100 - index),
|
|
44
|
+
metadata: {
|
|
45
|
+
provider: exports.RAW_TCP_HOLE_PUNCH_TRANSPORT,
|
|
46
|
+
bucket: plan.bucket,
|
|
47
|
+
boundary: plan.boundary,
|
|
48
|
+
rendezvousTimeSeconds: plan.rendezvousTimeSeconds,
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
async function createSignaledRawTcpP2PSession(options) {
|
|
53
|
+
const plan = options.plan ?? (0, TcpP2PDataPlaneTransport_1.computeTcpHolePunchPlan)(options.planOptions);
|
|
54
|
+
const localCandidates = createRawTcpHolePunchCandidates({
|
|
55
|
+
role: 'client',
|
|
56
|
+
sourceId: options.clientId,
|
|
57
|
+
host: options.host,
|
|
58
|
+
address: options.address,
|
|
59
|
+
createdAt: options.createdAt,
|
|
60
|
+
priority: options.priority,
|
|
61
|
+
plan,
|
|
62
|
+
candidateIdPrefix: options.candidateIdPrefix,
|
|
63
|
+
});
|
|
64
|
+
const request = {
|
|
65
|
+
clientId: options.clientId,
|
|
66
|
+
capabilities: uniqueStrings([
|
|
67
|
+
exports.RAW_TCP_HOLE_PUNCH_CAPABILITY,
|
|
68
|
+
...(options.capabilities ?? []),
|
|
69
|
+
]),
|
|
70
|
+
candidates: localCandidates,
|
|
71
|
+
};
|
|
72
|
+
const session = await options.signaling.createP2PSession(request);
|
|
73
|
+
const effectiveLocalCandidates = localRawTcpCandidatesFromSessionOrFallback(session, localCandidates, 'client', options.clientId, plan.bucket);
|
|
74
|
+
return {
|
|
75
|
+
session,
|
|
76
|
+
plan,
|
|
77
|
+
localCandidates: effectiveLocalCandidates,
|
|
78
|
+
rawTcpRoute: selectRawTcpP2PRoute(session.nodeCandidates),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function answerPendingRawTcpP2PSessionsOnce(options) {
|
|
82
|
+
const sessions = await options.signaling.listP2PSessions();
|
|
83
|
+
const answered = [];
|
|
84
|
+
for (const session of sessions) {
|
|
85
|
+
if (!selectRawTcpP2PRoute(session.nodeCandidates)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const plan = planFromRemoteCandidates(session.candidates, 'client');
|
|
89
|
+
if (!plan) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (hasRawTcpCandidate(session.candidates, 'node', options.sourceId, plan.bucket)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const candidates = createRawTcpHolePunchCandidates({
|
|
96
|
+
role: 'node',
|
|
97
|
+
sourceId: options.sourceId,
|
|
98
|
+
host: options.host,
|
|
99
|
+
address: options.address,
|
|
100
|
+
createdAt: options.createdAt,
|
|
101
|
+
priority: options.priority,
|
|
102
|
+
plan,
|
|
103
|
+
candidateIdPrefix: options.candidateIdPrefix,
|
|
104
|
+
});
|
|
105
|
+
answered.push(await options.signaling.addP2PCandidates(session.signalingUrl || session.sessionId, {
|
|
106
|
+
role: 'node',
|
|
107
|
+
sourceId: options.sourceId,
|
|
108
|
+
candidates,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
return answered;
|
|
112
|
+
}
|
|
113
|
+
async function acceptSignaledRawTcpP2PConnectionOnce(options) {
|
|
114
|
+
const sessions = await options.signaling.listP2PSessions();
|
|
115
|
+
for (const session of sessions) {
|
|
116
|
+
if (!selectRawTcpP2PRoute(session.nodeCandidates)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const plan = planFromRemoteCandidates(session.candidates, 'client');
|
|
120
|
+
if (!plan) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const localCandidates = createRawTcpHolePunchCandidates({
|
|
124
|
+
role: 'node',
|
|
125
|
+
sourceId: options.sourceId,
|
|
126
|
+
host: options.host,
|
|
127
|
+
address: options.address,
|
|
128
|
+
createdAt: options.createdAt,
|
|
129
|
+
priority: options.priority,
|
|
130
|
+
plan,
|
|
131
|
+
candidateIdPrefix: options.candidateIdPrefix,
|
|
132
|
+
});
|
|
133
|
+
const answeredSession = hasRawTcpCandidate(session.candidates, 'node', options.sourceId, plan.bucket)
|
|
134
|
+
? session
|
|
135
|
+
: await options.signaling.addP2PCandidates(session.signalingUrl || session.sessionId, {
|
|
136
|
+
role: 'node',
|
|
137
|
+
sourceId: options.sourceId,
|
|
138
|
+
candidates: localCandidates,
|
|
139
|
+
});
|
|
140
|
+
const remoteCandidates = filterRawTcpRemoteCandidates(answeredSession.candidates, 'node', options.sourceId, plan.bucket);
|
|
141
|
+
if (remoteCandidates.length === 0) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const effectiveLocalCandidates = localRawTcpCandidatesFromSessionOrFallback(answeredSession, localCandidates, 'node', options.sourceId, plan.bucket);
|
|
145
|
+
const socket = await connectRawTcpP2PSocket({
|
|
146
|
+
localCandidates: effectiveLocalCandidates,
|
|
147
|
+
remoteCandidates,
|
|
148
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
149
|
+
winnerSelectionWindowMs: options.winnerSelectionWindowMs,
|
|
150
|
+
localAddress: options.localAddress,
|
|
151
|
+
nowMs: options.nowMs,
|
|
152
|
+
sleepMs: options.sleepMs,
|
|
153
|
+
connectSocket: options.connectSocket,
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
session: answeredSession,
|
|
157
|
+
plan,
|
|
158
|
+
localCandidates: effectiveLocalCandidates,
|
|
159
|
+
remoteCandidates,
|
|
160
|
+
socketHandle: (0, TcpP2PDataPlaneTransport_1.attachTcpP2PDataPlaneSocket)({ socket, handler: options.handler }),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
async function waitForRawTcpRemoteCandidates(options) {
|
|
166
|
+
const startedAt = Date.now();
|
|
167
|
+
const timeoutMs = positiveInteger(options.timeoutMs, DEFAULT_WAIT_TIMEOUT_MS);
|
|
168
|
+
const pollIntervalMs = positiveInteger(options.pollIntervalMs, DEFAULT_POLL_INTERVAL_MS);
|
|
169
|
+
for (;;) {
|
|
170
|
+
const session = await options.signaling.getP2PSession(options.sessionIdOrUrl);
|
|
171
|
+
const candidates = filterRawTcpRemoteCandidates(session.candidates, options.localRole, options.localSourceId, options.bucket);
|
|
172
|
+
if (candidates.length > 0) {
|
|
173
|
+
return candidates;
|
|
174
|
+
}
|
|
175
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
176
|
+
throw new Error(`Timed out waiting for raw TCP P2P candidates after ${timeoutMs}ms`);
|
|
177
|
+
}
|
|
178
|
+
await sleep(Math.min(pollIntervalMs, Math.max(1, timeoutMs - (Date.now() - startedAt))));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function connectSignaledRawTcpP2PTransport(options) {
|
|
182
|
+
const signaled = await createSignaledRawTcpP2PSession(options);
|
|
183
|
+
const remoteCandidates = await waitForRawTcpRemoteCandidates({
|
|
184
|
+
signaling: options.signaling,
|
|
185
|
+
sessionIdOrUrl: signaled.session.signalingUrl || signaled.session.sessionId,
|
|
186
|
+
localRole: 'client',
|
|
187
|
+
localSourceId: options.clientId,
|
|
188
|
+
bucket: signaled.plan.bucket,
|
|
189
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
190
|
+
timeoutMs: options.waitTimeoutMs,
|
|
191
|
+
});
|
|
192
|
+
const transport = await connectRawTcpP2PTransport({
|
|
193
|
+
localCandidates: signaled.localCandidates,
|
|
194
|
+
remoteCandidates,
|
|
195
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
196
|
+
timeoutMs: options.timeoutMs,
|
|
197
|
+
winnerSelectionWindowMs: options.winnerSelectionWindowMs,
|
|
198
|
+
localAddress: options.localAddress,
|
|
199
|
+
nowMs: options.nowMs,
|
|
200
|
+
sleepMs: options.sleepMs,
|
|
201
|
+
connectSocket: options.connectSocket,
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
...signaled,
|
|
205
|
+
remoteCandidates,
|
|
206
|
+
transport,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function connectRawTcpP2PTransport(options) {
|
|
210
|
+
const { attempt, socket } = await connectRawTcpP2PSocketAttempt(options);
|
|
211
|
+
return (0, TcpP2PDataPlaneTransport_1.createTcpP2PDataPlaneTransport)({
|
|
212
|
+
remoteHost: attempt.remoteHost,
|
|
213
|
+
remotePort: attempt.remotePort,
|
|
214
|
+
socket,
|
|
215
|
+
timeoutMs: options.timeoutMs,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async function connectRawTcpP2PSocket(options) {
|
|
219
|
+
const { socket } = await connectRawTcpP2PSocketAttempt(options);
|
|
220
|
+
return socket;
|
|
221
|
+
}
|
|
222
|
+
async function connectRawTcpP2PSocketAttempt(options) {
|
|
223
|
+
const pairs = candidatePairs(options.localCandidates, options.remoteCandidates);
|
|
224
|
+
if (pairs.length === 0) {
|
|
225
|
+
throw new Error('No compatible raw TCP P2P candidate pairs');
|
|
226
|
+
}
|
|
227
|
+
const errors = [];
|
|
228
|
+
const nowMs = options.nowMs ?? Date.now;
|
|
229
|
+
const sleepMs = options.sleepMs ?? sleep;
|
|
230
|
+
const connectSocket = options.connectSocket ?? defaultNodeRawTcpP2PConnectSocket;
|
|
231
|
+
const attempts = pairs.map(({ local, remote }) => createRawTcpConnectAttempt(local, remote, {
|
|
232
|
+
timeoutMs: positiveInteger(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS),
|
|
233
|
+
localAddress: options.localAddress,
|
|
234
|
+
}));
|
|
235
|
+
try {
|
|
236
|
+
const { attempt, socket } = await connectFirstRawTcpAttempt(attempts, {
|
|
237
|
+
nowMs,
|
|
238
|
+
sleepMs,
|
|
239
|
+
connectSocket,
|
|
240
|
+
winnerSelectionWindowMs: nonNegativeInteger(options.winnerSelectionWindowMs, 0),
|
|
241
|
+
});
|
|
242
|
+
return { attempt, socket };
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
246
|
+
}
|
|
247
|
+
throw new Error(`Failed to connect raw TCP P2P candidates: ${errors.join('; ')}`);
|
|
248
|
+
}
|
|
249
|
+
function filterRawTcpRemoteCandidates(candidates, localRole, localSourceId, bucket) {
|
|
250
|
+
return candidates.filter((candidate) => {
|
|
251
|
+
if (!isRawTcpHolePunchCandidate(candidate)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
if (candidate.role === localRole && candidate.sourceId === localSourceId) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (bucket !== undefined && getNumericMetadata(candidate, 'bucket') !== bucket) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function selectRawTcpP2PRoute(routes) {
|
|
264
|
+
return routes.find((route) => {
|
|
265
|
+
if (route.kind !== 'p2p') {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
if (route.targetUrl.startsWith('tcp-punch://')) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
const protocols = isRecord(route.metadata?.protocols) ? route.metadata.protocols : undefined;
|
|
272
|
+
const rawTcp = isRecord(protocols?.[exports.RAW_TCP_HOLE_PUNCH_TRANSPORT])
|
|
273
|
+
? protocols[exports.RAW_TCP_HOLE_PUNCH_TRANSPORT]
|
|
274
|
+
: undefined;
|
|
275
|
+
return rawTcp?.enabled === true;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function isRawTcpHolePunchCandidate(candidate) {
|
|
279
|
+
return candidate.protocol === 'tcp'
|
|
280
|
+
&& candidate.transport === exports.RAW_TCP_HOLE_PUNCH_TRANSPORT
|
|
281
|
+
&& getNumericMetadata(candidate, 'bucket') !== undefined
|
|
282
|
+
&& getNumericMetadata(candidate, 'rendezvousTimeSeconds') !== undefined
|
|
283
|
+
&& typeof candidate.port === 'number';
|
|
284
|
+
}
|
|
285
|
+
function localRawTcpCandidatesFromSessionOrFallback(session, fallback, role, sourceId, bucket) {
|
|
286
|
+
const signaledCandidates = session.candidates.filter((candidate) => candidate.role === role
|
|
287
|
+
&& candidate.sourceId === sourceId
|
|
288
|
+
&& isRawTcpHolePunchCandidate(candidate)
|
|
289
|
+
&& getNumericMetadata(candidate, 'bucket') === bucket);
|
|
290
|
+
return signaledCandidates.length > 0 ? signaledCandidates : fallback;
|
|
291
|
+
}
|
|
292
|
+
function candidatePairs(localCandidates, remoteCandidates) {
|
|
293
|
+
const local = localCandidates.filter(isRawTcpHolePunchCandidate).sort(compareCandidatePriority);
|
|
294
|
+
const remote = remoteCandidates
|
|
295
|
+
.filter(isRawTcpHolePunchCandidate)
|
|
296
|
+
.filter((candidate) => candidateHost(candidate).length > 0)
|
|
297
|
+
.sort(compareCandidatePriority);
|
|
298
|
+
const result = [];
|
|
299
|
+
for (const localCandidate of local) {
|
|
300
|
+
const localBucket = getNumericMetadata(localCandidate, 'bucket');
|
|
301
|
+
for (const remoteCandidate of remote) {
|
|
302
|
+
if (localBucket === getNumericMetadata(remoteCandidate, 'bucket')) {
|
|
303
|
+
result.push({ local: localCandidate, remote: remoteCandidate });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
function createRawTcpConnectAttempt(local, remote, options) {
|
|
310
|
+
const remoteHost = candidateHost(remote);
|
|
311
|
+
const remotePort = remote.port;
|
|
312
|
+
if (!remoteHost || !remotePort) {
|
|
313
|
+
throw new Error(`Remote raw TCP candidate ${remote.id} is missing host or port`);
|
|
314
|
+
}
|
|
315
|
+
const localRendezvousTimeSeconds = getNumericMetadata(local, 'rendezvousTimeSeconds') ?? 0;
|
|
316
|
+
const remoteRendezvousTimeSeconds = getNumericMetadata(remote, 'rendezvousTimeSeconds') ?? 0;
|
|
317
|
+
return {
|
|
318
|
+
local,
|
|
319
|
+
remote,
|
|
320
|
+
remoteHost,
|
|
321
|
+
remotePort,
|
|
322
|
+
localAddress: options.localAddress,
|
|
323
|
+
...(local.port ? { localPort: local.port } : {}),
|
|
324
|
+
rendezvousTimeMs: Math.max(localRendezvousTimeSeconds, remoteRendezvousTimeSeconds) * 1_000,
|
|
325
|
+
timeoutMs: options.timeoutMs,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
async function connectFirstRawTcpAttempt(attempts, options) {
|
|
329
|
+
return new Promise((resolve, reject) => {
|
|
330
|
+
const controllers = attempts.map(() => new AbortController());
|
|
331
|
+
const errors = [];
|
|
332
|
+
const successes = [];
|
|
333
|
+
let pending = attempts.length;
|
|
334
|
+
let settled = false;
|
|
335
|
+
let settleTimer;
|
|
336
|
+
const clearSettleTimer = () => {
|
|
337
|
+
if (settleTimer) {
|
|
338
|
+
clearTimeout(settleTimer);
|
|
339
|
+
settleTimer = undefined;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const finishReject = (error) => {
|
|
343
|
+
if (settled) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
settled = true;
|
|
347
|
+
clearSettleTimer();
|
|
348
|
+
reject(error);
|
|
349
|
+
};
|
|
350
|
+
const finishWithWinner = () => {
|
|
351
|
+
if (settled) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (successes.length === 0) {
|
|
355
|
+
finishReject(new Error(errors.join('; ')));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
settled = true;
|
|
359
|
+
clearSettleTimer();
|
|
360
|
+
const winner = selectRawTcpWinner(successes);
|
|
361
|
+
controllers.forEach((controller, index) => {
|
|
362
|
+
if (index !== winner.index) {
|
|
363
|
+
controller.abort();
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
for (const success of successes) {
|
|
367
|
+
if (success !== winner) {
|
|
368
|
+
success.socket.destroy();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
resolve({ attempt: winner.attempt, socket: winner.socket });
|
|
372
|
+
};
|
|
373
|
+
const scheduleWinner = () => {
|
|
374
|
+
if (options.winnerSelectionWindowMs <= 0 || pending === 0) {
|
|
375
|
+
finishWithWinner();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
settleTimer ??= setTimeout(finishWithWinner, options.winnerSelectionWindowMs);
|
|
379
|
+
};
|
|
380
|
+
attempts.forEach((baseAttempt, index) => {
|
|
381
|
+
const controller = controllers[index];
|
|
382
|
+
const attempt = {
|
|
383
|
+
...baseAttempt,
|
|
384
|
+
signal: controller.signal,
|
|
385
|
+
};
|
|
386
|
+
void runRawTcpConnectAttempt(attempt, options)
|
|
387
|
+
.then((socket) => {
|
|
388
|
+
if (settled) {
|
|
389
|
+
socket.destroy();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
successes.push({ attempt, socket, index });
|
|
393
|
+
pending -= 1;
|
|
394
|
+
scheduleWinner();
|
|
395
|
+
})
|
|
396
|
+
.catch((error) => {
|
|
397
|
+
if (settled) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
401
|
+
pending -= 1;
|
|
402
|
+
if (pending === 0) {
|
|
403
|
+
if (successes.length > 0) {
|
|
404
|
+
finishWithWinner();
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
finishReject(new Error(errors.join('; ')));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function selectRawTcpWinner(successes) {
|
|
415
|
+
return successes.slice().sort((left, right) => {
|
|
416
|
+
const keyCompare = rawTcpAttemptPairKey(left.attempt).localeCompare(rawTcpAttemptPairKey(right.attempt));
|
|
417
|
+
return keyCompare === 0 ? left.index - right.index : keyCompare;
|
|
418
|
+
})[0];
|
|
419
|
+
}
|
|
420
|
+
function rawTcpAttemptPairKey(attempt) {
|
|
421
|
+
const endpoints = [
|
|
422
|
+
rawTcpEndpointKey(attempt.local),
|
|
423
|
+
rawTcpEndpointKey(attempt.remote),
|
|
424
|
+
].sort();
|
|
425
|
+
return endpoints.join('<->');
|
|
426
|
+
}
|
|
427
|
+
function rawTcpEndpointKey(candidate) {
|
|
428
|
+
return [
|
|
429
|
+
candidate.sourceId,
|
|
430
|
+
candidateHost(candidate),
|
|
431
|
+
String(candidate.port ?? ''),
|
|
432
|
+
candidate.id,
|
|
433
|
+
].join('|');
|
|
434
|
+
}
|
|
435
|
+
async function runRawTcpConnectAttempt(attempt, options) {
|
|
436
|
+
await waitForRendezvous(attempt.rendezvousTimeMs, options.nowMs, options.sleepMs, attempt.signal);
|
|
437
|
+
return connectSocketWithTimeout(attempt, options.connectSocket);
|
|
438
|
+
}
|
|
439
|
+
async function waitForRendezvous(rendezvousTimeMs, nowMs, sleepMs, signal) {
|
|
440
|
+
const delayMs = rendezvousTimeMs - nowMs();
|
|
441
|
+
if (delayMs > 0) {
|
|
442
|
+
await abortableSleep(delayMs, sleepMs, signal);
|
|
443
|
+
}
|
|
444
|
+
if (signal?.aborted) {
|
|
445
|
+
throw new Error('Raw TCP candidate connect aborted');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async function connectSocketWithTimeout(attempt, connectSocket) {
|
|
449
|
+
return new Promise((resolve, reject) => {
|
|
450
|
+
let finished = false;
|
|
451
|
+
const timeout = setTimeout(() => {
|
|
452
|
+
finishReject(new Error(`Raw TCP candidate connect timed out after ${attempt.timeoutMs}ms`));
|
|
453
|
+
}, attempt.timeoutMs);
|
|
454
|
+
const onAbort = () => {
|
|
455
|
+
finishReject(new Error('Raw TCP candidate connect aborted'));
|
|
456
|
+
};
|
|
457
|
+
const cleanup = () => {
|
|
458
|
+
clearTimeout(timeout);
|
|
459
|
+
attempt.signal?.removeEventListener('abort', onAbort);
|
|
460
|
+
};
|
|
461
|
+
const finishReject = (error) => {
|
|
462
|
+
if (finished) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
finished = true;
|
|
466
|
+
cleanup();
|
|
467
|
+
reject(error);
|
|
468
|
+
};
|
|
469
|
+
attempt.signal?.addEventListener('abort', onAbort, { once: true });
|
|
470
|
+
if (attempt.signal?.aborted) {
|
|
471
|
+
finishReject(new Error('Raw TCP candidate connect aborted'));
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
void connectSocket(attempt)
|
|
475
|
+
.then((socket) => {
|
|
476
|
+
if (finished) {
|
|
477
|
+
socket.destroy();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
finished = true;
|
|
481
|
+
cleanup();
|
|
482
|
+
resolve(socket);
|
|
483
|
+
})
|
|
484
|
+
.catch((error) => {
|
|
485
|
+
finishReject(error instanceof Error ? error : new Error(String(error)));
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
async function connectNodeRawTcpSocket(attempt, options) {
|
|
490
|
+
const startedAt = Date.now();
|
|
491
|
+
const retryIntervalMs = positiveInteger(options.retryIntervalMs, DEFAULT_RAW_TCP_RETRY_INTERVAL_MS);
|
|
492
|
+
let lastError;
|
|
493
|
+
for (;;) {
|
|
494
|
+
if (attempt.signal?.aborted) {
|
|
495
|
+
options.onEvent?.({ type: 'aborted', attempt });
|
|
496
|
+
throw new Error('Raw TCP candidate connect aborted');
|
|
497
|
+
}
|
|
498
|
+
const remainingMs = attempt.timeoutMs - (Date.now() - startedAt);
|
|
499
|
+
if (remainingMs <= 0) {
|
|
500
|
+
const error = new Error(`Raw TCP candidate connect timed out after ${attempt.timeoutMs}ms${lastError ? `: ${lastError.message}` : ''}`);
|
|
501
|
+
options.onEvent?.({ type: 'timeout', attempt, error });
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
options.onEvent?.({ type: 'attempt', attempt, remainingMs });
|
|
505
|
+
try {
|
|
506
|
+
const socket = await connectRawTcpSocketOnce(attempt, remainingMs);
|
|
507
|
+
options.onEvent?.({ type: 'success', attempt, socket });
|
|
508
|
+
return socket;
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
512
|
+
if (attempt.signal?.aborted) {
|
|
513
|
+
options.onEvent?.({ type: 'aborted', attempt, error: lastError });
|
|
514
|
+
throw new Error('Raw TCP candidate connect aborted');
|
|
515
|
+
}
|
|
516
|
+
const remainingAfterFailureMs = attempt.timeoutMs - (Date.now() - startedAt);
|
|
517
|
+
const retryDelayMs = Math.min(retryIntervalMs, Math.max(0, remainingAfterFailureMs));
|
|
518
|
+
if (retryDelayMs <= 0) {
|
|
519
|
+
const timeoutError = new Error(`Raw TCP candidate connect timed out after ${attempt.timeoutMs}ms: ${lastError.message}`);
|
|
520
|
+
options.onEvent?.({ type: 'timeout', attempt, error: timeoutError });
|
|
521
|
+
throw timeoutError;
|
|
522
|
+
}
|
|
523
|
+
options.onEvent?.({
|
|
524
|
+
type: 'retry',
|
|
525
|
+
attempt,
|
|
526
|
+
error: lastError,
|
|
527
|
+
remainingMs: remainingAfterFailureMs,
|
|
528
|
+
retryDelayMs,
|
|
529
|
+
});
|
|
530
|
+
try {
|
|
531
|
+
await sleep(retryDelayMs, attempt.signal);
|
|
532
|
+
}
|
|
533
|
+
catch (sleepError) {
|
|
534
|
+
if (attempt.signal?.aborted) {
|
|
535
|
+
options.onEvent?.({
|
|
536
|
+
type: 'aborted',
|
|
537
|
+
attempt,
|
|
538
|
+
error: sleepError instanceof Error ? sleepError : new Error(String(sleepError)),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
throw sleepError;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function connectRawTcpSocketOnce(attempt, timeoutMs) {
|
|
547
|
+
return new Promise((resolve, reject) => {
|
|
548
|
+
const connectOptions = {
|
|
549
|
+
host: attempt.remoteHost,
|
|
550
|
+
port: attempt.remotePort,
|
|
551
|
+
localAddress: attempt.localAddress,
|
|
552
|
+
...(attempt.localPort ? { localPort: attempt.localPort } : {}),
|
|
553
|
+
};
|
|
554
|
+
const socket = (0, node_net_1.createConnection)(connectOptions);
|
|
555
|
+
const timeout = setTimeout(() => {
|
|
556
|
+
cleanup();
|
|
557
|
+
socket.destroy();
|
|
558
|
+
reject(new Error(`Raw TCP candidate connect attempt timed out after ${timeoutMs}ms`));
|
|
559
|
+
}, timeoutMs);
|
|
560
|
+
const cleanup = () => {
|
|
561
|
+
clearTimeout(timeout);
|
|
562
|
+
socket.off('connect', onConnect);
|
|
563
|
+
socket.off('error', onError);
|
|
564
|
+
attempt.signal?.removeEventListener('abort', onAbort);
|
|
565
|
+
};
|
|
566
|
+
const onConnect = () => {
|
|
567
|
+
cleanup();
|
|
568
|
+
resolve(socket);
|
|
569
|
+
};
|
|
570
|
+
const onError = (error) => {
|
|
571
|
+
cleanup();
|
|
572
|
+
socket.destroy();
|
|
573
|
+
reject(error);
|
|
574
|
+
};
|
|
575
|
+
const onAbort = () => {
|
|
576
|
+
cleanup();
|
|
577
|
+
socket.destroy();
|
|
578
|
+
reject(new Error('Raw TCP candidate connect aborted'));
|
|
579
|
+
};
|
|
580
|
+
attempt.signal?.addEventListener('abort', onAbort, { once: true });
|
|
581
|
+
if (attempt.signal?.aborted) {
|
|
582
|
+
onAbort();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
socket.once('connect', onConnect);
|
|
586
|
+
socket.once('error', onError);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function candidateHost(candidate) {
|
|
590
|
+
return candidate.host ?? candidate.address ?? hostFromCandidateUrl(candidate.url) ?? '';
|
|
591
|
+
}
|
|
592
|
+
function hostFromCandidateUrl(value) {
|
|
593
|
+
if (!value) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
return new URL(value).hostname;
|
|
598
|
+
}
|
|
599
|
+
catch {
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function compareCandidatePriority(left, right) {
|
|
604
|
+
return (right.priority ?? 0) - (left.priority ?? 0);
|
|
605
|
+
}
|
|
606
|
+
function planFromRemoteCandidates(candidates, role) {
|
|
607
|
+
const rawCandidates = candidates
|
|
608
|
+
.filter((candidate) => candidate.role === role)
|
|
609
|
+
.filter(isRawTcpHolePunchCandidate);
|
|
610
|
+
const first = rawCandidates[0];
|
|
611
|
+
if (!first) {
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
const bucket = getNumericMetadata(first, 'bucket');
|
|
615
|
+
const boundary = getNumericMetadata(first, 'boundary');
|
|
616
|
+
const rendezvousTimeSeconds = getNumericMetadata(first, 'rendezvousTimeSeconds');
|
|
617
|
+
if (bucket === undefined || boundary === undefined || rendezvousTimeSeconds === undefined) {
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
const ports = rawCandidates
|
|
621
|
+
.filter((candidate) => getNumericMetadata(candidate, 'bucket') === bucket)
|
|
622
|
+
.map((candidate) => candidate.port)
|
|
623
|
+
.filter((port) => typeof port === 'number' && Number.isInteger(port) && port > 0);
|
|
624
|
+
if (ports.length === 0) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
bucket,
|
|
629
|
+
boundary,
|
|
630
|
+
rendezvousTimeSeconds,
|
|
631
|
+
ports: [...new Set(ports)].sort((a, b) => b - a),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function hasRawTcpCandidate(candidates, role, sourceId, bucket) {
|
|
635
|
+
return candidates.some((candidate) => candidate.role === role
|
|
636
|
+
&& candidate.sourceId === sourceId
|
|
637
|
+
&& isRawTcpHolePunchCandidate(candidate)
|
|
638
|
+
&& getNumericMetadata(candidate, 'bucket') === bucket);
|
|
639
|
+
}
|
|
640
|
+
function getNumericMetadata(candidate, key) {
|
|
641
|
+
const value = candidate.metadata?.[key];
|
|
642
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
643
|
+
}
|
|
644
|
+
function uniqueStrings(values) {
|
|
645
|
+
return [...new Set(values.filter((value) => value.trim().length > 0))];
|
|
646
|
+
}
|
|
647
|
+
function positiveInteger(value, fallback) {
|
|
648
|
+
return Number.isInteger(value) && value !== undefined && value > 0 ? value : fallback;
|
|
649
|
+
}
|
|
650
|
+
function nonNegativeInteger(value, fallback) {
|
|
651
|
+
return Number.isInteger(value) && value !== undefined && value >= 0 ? value : fallback;
|
|
652
|
+
}
|
|
653
|
+
function sleep(ms, signal) {
|
|
654
|
+
return new Promise((resolve, reject) => {
|
|
655
|
+
const timeout = setTimeout(() => {
|
|
656
|
+
cleanup();
|
|
657
|
+
resolve();
|
|
658
|
+
}, ms);
|
|
659
|
+
const onAbort = () => {
|
|
660
|
+
cleanup();
|
|
661
|
+
reject(new Error('Raw TCP candidate connect aborted'));
|
|
662
|
+
};
|
|
663
|
+
const cleanup = () => {
|
|
664
|
+
clearTimeout(timeout);
|
|
665
|
+
signal?.removeEventListener('abort', onAbort);
|
|
666
|
+
};
|
|
667
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
668
|
+
if (signal?.aborted) {
|
|
669
|
+
onAbort();
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
async function abortableSleep(ms, sleepMs, signal) {
|
|
674
|
+
if (!signal) {
|
|
675
|
+
await sleepMs(ms);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (signal.aborted) {
|
|
679
|
+
throw new Error('Raw TCP candidate connect aborted');
|
|
680
|
+
}
|
|
681
|
+
let removeAbortListener;
|
|
682
|
+
try {
|
|
683
|
+
await Promise.race([
|
|
684
|
+
sleepMs(ms, signal),
|
|
685
|
+
new Promise((_resolve, reject) => {
|
|
686
|
+
const onAbort = () => reject(new Error('Raw TCP candidate connect aborted'));
|
|
687
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
688
|
+
removeAbortListener = () => signal.removeEventListener('abort', onAbort);
|
|
689
|
+
}),
|
|
690
|
+
]);
|
|
691
|
+
}
|
|
692
|
+
finally {
|
|
693
|
+
removeAbortListener?.();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function isRecord(value) {
|
|
697
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
698
|
+
}
|
|
699
|
+
//# sourceMappingURL=TcpP2PSignalingSession.js.map
|