@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
|
@@ -512,6 +512,10 @@
|
|
|
512
512
|
"@id": "undefineds:dist/storage/rdf/PostgresRdfEngine.jsonld#PostgresRdfEngine__member_insertQuads",
|
|
513
513
|
"memberFieldName": "insertQuads"
|
|
514
514
|
},
|
|
515
|
+
{
|
|
516
|
+
"@id": "undefineds:dist/storage/rdf/PostgresRdfEngine.jsonld#PostgresRdfEngine__member_withRetryableWrite",
|
|
517
|
+
"memberFieldName": "withRetryableWrite"
|
|
518
|
+
},
|
|
515
519
|
{
|
|
516
520
|
"@id": "undefineds:dist/storage/rdf/PostgresRdfEngine.jsonld#PostgresRdfEngine__member_deleteExactQuad",
|
|
517
521
|
"memberFieldName": "deleteExactQuad"
|
|
@@ -2,3 +2,5 @@ export { startNoAuthXpod } from './no-auth-xpod';
|
|
|
2
2
|
export type { NoAuthXpodOptions } from './no-auth-xpod';
|
|
3
3
|
export { seedPod } from './seed-pod';
|
|
4
4
|
export type { SeedPodOptions } from './seed-pod';
|
|
5
|
+
export { runLocalManagedClientP2PE2ESmoke } from './local-managed-client-p2p-e2e-smoke';
|
|
6
|
+
export type { LocalManagedClientP2PE2ESmokeOptions, LocalManagedClientP2PE2ESmokeResult } from './local-managed-client-p2p-e2e-smoke';
|
package/dist/test-utils/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.seedPod = exports.startNoAuthXpod = void 0;
|
|
3
|
+
exports.runLocalManagedClientP2PE2ESmoke = exports.seedPod = exports.startNoAuthXpod = void 0;
|
|
4
4
|
var no_auth_xpod_1 = require("./no-auth-xpod");
|
|
5
5
|
Object.defineProperty(exports, "startNoAuthXpod", { enumerable: true, get: function () { return no_auth_xpod_1.startNoAuthXpod; } });
|
|
6
6
|
var seed_pod_1 = require("./seed-pod");
|
|
7
7
|
Object.defineProperty(exports, "seedPod", { enumerable: true, get: function () { return seed_pod_1.seedPod; } });
|
|
8
|
+
var local_managed_client_p2p_e2e_smoke_1 = require("./local-managed-client-p2p-e2e-smoke");
|
|
9
|
+
Object.defineProperty(exports, "runLocalManagedClientP2PE2ESmoke", { enumerable: true, get: function () { return local_managed_client_p2p_e2e_smoke_1.runLocalManagedClientP2PE2ESmoke; } });
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/test-utils/index.ts"],"names":[],"mappings":";;;AAAA,+CAAiD;AAAxC,+GAAA,eAAe,OAAA;AAExB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA","sourcesContent":["export { startNoAuthXpod } from './no-auth-xpod';\nexport type { NoAuthXpodOptions } from './no-auth-xpod';\nexport { seedPod } from './seed-pod';\nexport type { SeedPodOptions } from './seed-pod';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/test-utils/index.ts"],"names":[],"mappings":";;;AAAA,+CAAiD;AAAxC,+GAAA,eAAe,OAAA;AAExB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA;AAEhB,2FAAwF;AAA/E,sJAAA,gCAAgC,OAAA","sourcesContent":["export { startNoAuthXpod } from './no-auth-xpod';\nexport type { NoAuthXpodOptions } from './no-auth-xpod';\nexport { seedPod } from './seed-pod';\nexport type { SeedPodOptions } from './seed-pod';\nexport { runLocalManagedClientP2PE2ESmoke } from './local-managed-client-p2p-e2e-smoke';\nexport type { LocalManagedClientP2PE2ESmokeOptions, LocalManagedClientP2PE2ESmokeResult } from './local-managed-client-p2p-e2e-smoke';\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ManagedClientP2PSmokeResult, type NodeRawTcpP2PConnectSocketEvent, type RawTcpP2PConnectAttempt } from '../edge/reachability';
|
|
2
|
+
export type LocalManagedClientP2PSocketMode = 'deterministic-injection' | 'real-tcp-listener';
|
|
3
|
+
export interface LocalManagedClientP2PPlan {
|
|
4
|
+
bucket: number;
|
|
5
|
+
boundary: number;
|
|
6
|
+
rendezvousTimeSeconds: number;
|
|
7
|
+
ports: number[];
|
|
8
|
+
}
|
|
9
|
+
export interface LocalManagedClientP2PE2ESmokeOptions {
|
|
10
|
+
nodeName?: string;
|
|
11
|
+
clientId?: string;
|
|
12
|
+
baseStorageDomain?: string;
|
|
13
|
+
resourcePath?: string;
|
|
14
|
+
targetBody?: string;
|
|
15
|
+
p2pHost?: string;
|
|
16
|
+
advertiseClientHost?: boolean;
|
|
17
|
+
advertiseNodeHost?: boolean;
|
|
18
|
+
routeWaitTimeoutMs?: number;
|
|
19
|
+
pollIntervalMs?: number;
|
|
20
|
+
connectTimeoutMs?: number;
|
|
21
|
+
requestTimeoutMs?: number;
|
|
22
|
+
socketMode?: LocalManagedClientP2PSocketMode;
|
|
23
|
+
}
|
|
24
|
+
export interface LocalManagedClientP2PE2ESmokeResult {
|
|
25
|
+
smokeOk: boolean;
|
|
26
|
+
nodeId: string;
|
|
27
|
+
apiBaseUrl: string;
|
|
28
|
+
targetBaseUrl: string;
|
|
29
|
+
resourceUrl: string;
|
|
30
|
+
plan: LocalManagedClientP2PPlan;
|
|
31
|
+
clientPlan: LocalManagedClientP2PPlan;
|
|
32
|
+
nodePlan: LocalManagedClientP2PPlan;
|
|
33
|
+
smoke: ManagedClientP2PSmokeResult;
|
|
34
|
+
p2pAttempts: {
|
|
35
|
+
client: RawTcpP2PConnectAttempt[];
|
|
36
|
+
node: RawTcpP2PConnectAttempt[];
|
|
37
|
+
};
|
|
38
|
+
connectorEvents: {
|
|
39
|
+
client: LocalManagedClientP2PConnectorEvent[];
|
|
40
|
+
node: LocalManagedClientP2PConnectorEvent[];
|
|
41
|
+
};
|
|
42
|
+
targetRequests: Array<{
|
|
43
|
+
method: string;
|
|
44
|
+
url: string;
|
|
45
|
+
headers: Record<string, string | string[] | undefined>;
|
|
46
|
+
}>;
|
|
47
|
+
evidence: {
|
|
48
|
+
routeDiscovery: 'p2p-route-published';
|
|
49
|
+
signaling: 'repository-backed-api';
|
|
50
|
+
dataPlane: 'deterministic-socket-injection' | 'real-local-tcp-listener';
|
|
51
|
+
canonicalFetch: 'xpod-p2p-http/1';
|
|
52
|
+
clientAddress: 'explicit-host' | 'signal-observed';
|
|
53
|
+
nodeAddress: 'explicit-host' | 'signal-observed';
|
|
54
|
+
};
|
|
55
|
+
caveats: string[];
|
|
56
|
+
}
|
|
57
|
+
export interface LocalManagedClientP2PConnectorEvent {
|
|
58
|
+
type: NodeRawTcpP2PConnectSocketEvent['type'];
|
|
59
|
+
localPort?: number;
|
|
60
|
+
remotePort: number;
|
|
61
|
+
message?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function runLocalManagedClientP2PE2ESmoke(options?: LocalManagedClientP2PE2ESmokeOptions): Promise<LocalManagedClientP2PE2ESmokeResult>;
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runLocalManagedClientP2PE2ESmoke = runLocalManagedClientP2PE2ESmoke;
|
|
4
|
+
const node_http_1 = require("node:http");
|
|
5
|
+
const node_net_1 = require("node:net");
|
|
6
|
+
const ApiServer_1 = require("../api/ApiServer");
|
|
7
|
+
const MultiAuthenticator_1 = require("../api/auth/MultiAuthenticator");
|
|
8
|
+
const NodeTokenAuthenticator_1 = require("../api/auth/NodeTokenAuthenticator");
|
|
9
|
+
const ServiceTokenAuthenticator_1 = require("../api/auth/ServiceTokenAuthenticator");
|
|
10
|
+
const EdgeNodeSignalHandler_1 = require("../api/handlers/EdgeNodeSignalHandler");
|
|
11
|
+
const ReachabilityHandler_1 = require("../api/handlers/ReachabilityHandler");
|
|
12
|
+
const AuthMiddleware_1 = require("../api/middleware/AuthMiddleware");
|
|
13
|
+
const EdgeNodeAgent_1 = require("../edge/EdgeNodeAgent");
|
|
14
|
+
const reachability_1 = require("../edge/reachability");
|
|
15
|
+
const EdgeNodeRepository_1 = require("../identity/drizzle/EdgeNodeRepository");
|
|
16
|
+
const db_1 = require("../identity/drizzle/db");
|
|
17
|
+
const ServiceTokenRepository_1 = require("../identity/drizzle/ServiceTokenRepository");
|
|
18
|
+
async function runLocalManagedClientP2PE2ESmoke(options = {}) {
|
|
19
|
+
const cleanupStack = [];
|
|
20
|
+
const nodeName = options.nodeName ?? 'local-p2p-node';
|
|
21
|
+
const clientId = options.clientId ?? `managed-client-${process.pid}`;
|
|
22
|
+
const baseStorageDomain = options.baseStorageDomain ?? 'pods.example';
|
|
23
|
+
const resourcePath = normalizeResourcePath(options.resourcePath ?? '/alice/local-p2p-e2e.txt?version=1');
|
|
24
|
+
const targetBody = options.targetBody ?? 'local p2p e2e response';
|
|
25
|
+
const p2pHost = options.p2pHost ?? '127.0.0.1';
|
|
26
|
+
const advertiseClientHost = options.advertiseClientHost ?? true;
|
|
27
|
+
const advertiseNodeHost = options.advertiseNodeHost ?? true;
|
|
28
|
+
const routeWaitTimeoutMs = options.routeWaitTimeoutMs ?? 2_000;
|
|
29
|
+
const pollIntervalMs = options.pollIntervalMs ?? 10;
|
|
30
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? 1_000;
|
|
31
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 2_000;
|
|
32
|
+
const socketMode = options.socketMode ?? 'deterministic-injection';
|
|
33
|
+
try {
|
|
34
|
+
const db = (0, db_1.getIdentityDatabase)(`sqlite::memory:p2p-local-e2e-${Date.now()}-${Math.random()}`);
|
|
35
|
+
const nodeRepo = new EdgeNodeRepository_1.EdgeNodeRepository(db);
|
|
36
|
+
const serviceTokenRepo = new ServiceTokenRepository_1.ServiceTokenRepository(db);
|
|
37
|
+
const { nodeId, token: nodeToken } = await nodeRepo.createNode(nodeName);
|
|
38
|
+
const { token: serviceToken } = await serviceTokenRepo.createToken({
|
|
39
|
+
serviceType: 'cloud',
|
|
40
|
+
serviceId: 'managed-client-smoke',
|
|
41
|
+
scopes: ['reachability:read', 'reachability:write'],
|
|
42
|
+
});
|
|
43
|
+
const signalApi = await startSignalApi({
|
|
44
|
+
nodeRepo,
|
|
45
|
+
serviceTokenRepo,
|
|
46
|
+
baseStorageDomain,
|
|
47
|
+
});
|
|
48
|
+
cleanupStack.push(() => signalApi.close());
|
|
49
|
+
const target = await startTargetServer({ resourcePath, targetBody });
|
|
50
|
+
cleanupStack.push(() => target.close());
|
|
51
|
+
const p2pAttempts = { client: [], node: [] };
|
|
52
|
+
const connectorEvents = { client: [], node: [] };
|
|
53
|
+
const clientPort = await reserveTcpPort();
|
|
54
|
+
const plan = {
|
|
55
|
+
bucket: 9_001,
|
|
56
|
+
boundary: 9_001,
|
|
57
|
+
rendezvousTimeSeconds: 0,
|
|
58
|
+
ports: [clientPort],
|
|
59
|
+
};
|
|
60
|
+
let nodePlan = plan;
|
|
61
|
+
let clientConnectSocket;
|
|
62
|
+
let responderDebug;
|
|
63
|
+
if (socketMode === 'real-tcp-listener') {
|
|
64
|
+
await publishP2PRoute({
|
|
65
|
+
repository: nodeRepo,
|
|
66
|
+
nodeId,
|
|
67
|
+
baseUrl: `https://${nodeId}.${baseStorageDomain}/`,
|
|
68
|
+
});
|
|
69
|
+
const dataPlaneServer = (0, reachability_1.createTcpP2PDataPlaneServer)({
|
|
70
|
+
handler: (0, reachability_1.createP2PDataPlaneHandler)({ targetBaseUrl: target.baseUrl }),
|
|
71
|
+
host: p2pHost,
|
|
72
|
+
});
|
|
73
|
+
await dataPlaneServer.listen(0);
|
|
74
|
+
nodePlan = {
|
|
75
|
+
...plan,
|
|
76
|
+
ports: [dataPlaneServer.address().port],
|
|
77
|
+
};
|
|
78
|
+
cleanupStack.push(() => dataPlaneServer.close());
|
|
79
|
+
const responder = startRealTcpNodeCandidateResponder({
|
|
80
|
+
apiBaseUrl: signalApi.baseUrl,
|
|
81
|
+
nodeId,
|
|
82
|
+
nodeToken,
|
|
83
|
+
host: advertiseNodeHost ? p2pHost : undefined,
|
|
84
|
+
clientPlan: plan,
|
|
85
|
+
nodePlan,
|
|
86
|
+
});
|
|
87
|
+
responderDebug = responder.diagnostics;
|
|
88
|
+
cleanupStack.push(() => responder.stop());
|
|
89
|
+
const nodeConnector = (0, reachability_1.createNodeRawTcpP2PConnectSocket)({
|
|
90
|
+
onEvent: (event) => connectorEvents.client.push(summarizeConnectorEvent(event)),
|
|
91
|
+
});
|
|
92
|
+
clientConnectSocket = async (attempt) => {
|
|
93
|
+
p2pAttempts.client.push(attempt);
|
|
94
|
+
return nodeConnector(attempt);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const socketPair = await createSocketPair();
|
|
99
|
+
cleanupStack.push(() => socketPair.close());
|
|
100
|
+
const agent = new EdgeNodeAgent_1.EdgeNodeAgent();
|
|
101
|
+
cleanupStack.push(() => agent.stop());
|
|
102
|
+
const initialHeartbeat = createDeferred();
|
|
103
|
+
let heartbeatResponseSeen = false;
|
|
104
|
+
await agent.start({
|
|
105
|
+
signalEndpoint: `${signalApi.baseUrl}/v1/signal`,
|
|
106
|
+
nodeId,
|
|
107
|
+
nodeToken,
|
|
108
|
+
baseUrl: `https://${nodeId}.${baseStorageDomain}/`,
|
|
109
|
+
enableNetworkDetection: false,
|
|
110
|
+
onHeartbeatResponse: () => {
|
|
111
|
+
heartbeatResponseSeen = true;
|
|
112
|
+
initialHeartbeat.resolve();
|
|
113
|
+
},
|
|
114
|
+
p2p: {
|
|
115
|
+
enabled: true,
|
|
116
|
+
targetBaseUrl: target.baseUrl,
|
|
117
|
+
...(advertiseNodeHost ? { host: p2pHost } : {}),
|
|
118
|
+
acceptIntervalMs: 20,
|
|
119
|
+
connectTimeoutMs,
|
|
120
|
+
winnerSelectionWindowMs: 0,
|
|
121
|
+
connectSocket: async (attempt) => {
|
|
122
|
+
p2pAttempts.node.push(attempt);
|
|
123
|
+
return socketPair.serverSocket;
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
clientConnectSocket = async (attempt) => {
|
|
128
|
+
p2pAttempts.client.push(attempt);
|
|
129
|
+
return socketPair.clientSocket;
|
|
130
|
+
};
|
|
131
|
+
try {
|
|
132
|
+
await initialHeartbeat.wait(routeWaitTimeoutMs, 'initial P2P heartbeat was not acknowledged before route discovery');
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const metadata = await nodeRepo.getNodeMetadata(nodeId);
|
|
136
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; heartbeatResponseSeen=${heartbeatResponseSeen}; signalApi=${signalApi.baseUrl}; nodeMetadata=${JSON.stringify(metadata?.metadata ?? null)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
await waitForRoute({
|
|
140
|
+
apiBaseUrl: signalApi.baseUrl,
|
|
141
|
+
nodeId,
|
|
142
|
+
token: serviceToken,
|
|
143
|
+
predicate: (route) => route.kind === 'p2p' && route.targetUrl.startsWith('tcp-punch://'),
|
|
144
|
+
timeoutMs: routeWaitTimeoutMs,
|
|
145
|
+
});
|
|
146
|
+
const resourceUrl = `https://${nodeId}.${baseStorageDomain}${resourcePath}`;
|
|
147
|
+
let smoke;
|
|
148
|
+
try {
|
|
149
|
+
smoke = await (0, reachability_1.runManagedClientP2PSmoke)({
|
|
150
|
+
apiBaseUrl: signalApi.baseUrl,
|
|
151
|
+
nodeId,
|
|
152
|
+
token: serviceToken,
|
|
153
|
+
clientId,
|
|
154
|
+
resourceUrl,
|
|
155
|
+
plan,
|
|
156
|
+
pollIntervalMs,
|
|
157
|
+
waitTimeoutMs: routeWaitTimeoutMs,
|
|
158
|
+
connectTimeoutMs,
|
|
159
|
+
timeoutMs: requestTimeoutMs,
|
|
160
|
+
...(advertiseClientHost ? { host: p2pHost } : {}),
|
|
161
|
+
...(clientConnectSocket ? { connectSocket: clientConnectSocket } : {}),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
+
throw new Error(`${message}; responder=${JSON.stringify(responderDebug?.() ?? null)}`);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
smokeOk: smoke.ok && smoke.route.kind === 'p2p',
|
|
170
|
+
nodeId,
|
|
171
|
+
apiBaseUrl: signalApi.baseUrl,
|
|
172
|
+
targetBaseUrl: target.baseUrl,
|
|
173
|
+
resourceUrl,
|
|
174
|
+
plan,
|
|
175
|
+
clientPlan: plan,
|
|
176
|
+
nodePlan,
|
|
177
|
+
smoke,
|
|
178
|
+
p2pAttempts,
|
|
179
|
+
connectorEvents,
|
|
180
|
+
targetRequests: target.requests,
|
|
181
|
+
evidence: {
|
|
182
|
+
routeDiscovery: 'p2p-route-published',
|
|
183
|
+
signaling: 'repository-backed-api',
|
|
184
|
+
dataPlane: socketMode === 'real-tcp-listener' ? 'real-local-tcp-listener' : 'deterministic-socket-injection',
|
|
185
|
+
canonicalFetch: 'xpod-p2p-http/1',
|
|
186
|
+
clientAddress: advertiseClientHost ? 'explicit-host' : 'signal-observed',
|
|
187
|
+
nodeAddress: advertiseNodeHost ? 'explicit-host' : 'signal-observed',
|
|
188
|
+
},
|
|
189
|
+
caveats: [
|
|
190
|
+
socketMode === 'real-tcp-listener'
|
|
191
|
+
? 'This local smoke uses a real loopback TCP listener, but still does not prove cross-NAT TCP simultaneous open.'
|
|
192
|
+
: 'This local smoke does not prove real cross-NAT TCP simultaneous open.',
|
|
193
|
+
'Cloudflare Tunnel and FRP/SakuraFRP remain independent user-tunnel fallback routes.',
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
while (cleanupStack.length > 0) {
|
|
199
|
+
const cleanup = cleanupStack.pop();
|
|
200
|
+
await cleanup();
|
|
201
|
+
}
|
|
202
|
+
await (0, db_1.closeAllIdentityConnections)();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function summarizeConnectorEvent(event) {
|
|
206
|
+
return {
|
|
207
|
+
type: event.type,
|
|
208
|
+
...(event.attempt.localPort ? { localPort: event.attempt.localPort } : {}),
|
|
209
|
+
remotePort: event.attempt.remotePort,
|
|
210
|
+
...(event.error ? { message: event.error.message } : {}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async function publishP2PRoute(options) {
|
|
214
|
+
await options.repository.updateNodeHeartbeat(options.nodeId, {
|
|
215
|
+
baseUrl: options.baseUrl,
|
|
216
|
+
routes: [
|
|
217
|
+
{
|
|
218
|
+
id: 'p2p-raw-tcp',
|
|
219
|
+
nodeId: options.nodeId,
|
|
220
|
+
canonicalUrl: options.baseUrl,
|
|
221
|
+
kind: 'p2p',
|
|
222
|
+
targetUrl: `tcp-punch://node/${encodeURIComponent(options.nodeId)}`,
|
|
223
|
+
priority: 40,
|
|
224
|
+
requiresManagedClient: true,
|
|
225
|
+
visibility: 'authorized-client',
|
|
226
|
+
health: 'healthy',
|
|
227
|
+
metadata: {
|
|
228
|
+
protocols: {
|
|
229
|
+
'raw-tcp-hole-punch': { enabled: true },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
}, new Date());
|
|
235
|
+
}
|
|
236
|
+
function startRealTcpNodeCandidateResponder(options) {
|
|
237
|
+
const signaling = (0, reachability_1.createP2PSignalingClient)({
|
|
238
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
239
|
+
nodeId: options.nodeId,
|
|
240
|
+
token: options.nodeToken,
|
|
241
|
+
});
|
|
242
|
+
let stopped = false;
|
|
243
|
+
const debugState = { listCount: 0, sessionCount: 0, addCount: 0, lastError: '', lastSessions: [] };
|
|
244
|
+
const run = async () => {
|
|
245
|
+
while (!stopped) {
|
|
246
|
+
try {
|
|
247
|
+
const sessions = await signaling.listP2PSessions();
|
|
248
|
+
debugState.listCount += 1;
|
|
249
|
+
debugState.sessionCount = sessions.length;
|
|
250
|
+
debugState.lastSessions = sessions.map((session) => ({ sessionId: session.sessionId, signalingUrl: session.signalingUrl, candidates: session.candidates.map((candidate) => ({ role: candidate.role, sourceId: candidate.sourceId, port: candidate.port, host: candidate.host, address: candidate.address, url: candidate.url, metadata: candidate.metadata })) }));
|
|
251
|
+
for (const session of sessions) {
|
|
252
|
+
const hasClientCandidate = session.candidates.some((candidate) => candidate.role === 'client'
|
|
253
|
+
&& candidate.metadata?.bucket === options.clientPlan.bucket);
|
|
254
|
+
const hasNodeCandidate = session.candidates.some((candidate) => candidate.role === 'node'
|
|
255
|
+
&& candidate.sourceId === options.nodeId
|
|
256
|
+
&& candidate.metadata?.bucket === options.nodePlan.bucket);
|
|
257
|
+
if (!hasClientCandidate || hasNodeCandidate) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const candidates = (0, reachability_1.createRawTcpHolePunchCandidates)({
|
|
261
|
+
role: 'node',
|
|
262
|
+
sourceId: options.nodeId,
|
|
263
|
+
host: options.host,
|
|
264
|
+
plan: options.nodePlan,
|
|
265
|
+
});
|
|
266
|
+
const answered = await signaling.addP2PCandidates(session.signalingUrl || session.sessionId, {
|
|
267
|
+
role: 'node',
|
|
268
|
+
sourceId: options.nodeId,
|
|
269
|
+
candidates,
|
|
270
|
+
});
|
|
271
|
+
debugState.addCount += 1;
|
|
272
|
+
debugState.lastSessions = [{ sessionId: answered.sessionId, signalingUrl: answered.signalingUrl, candidates: answered.candidates.map((candidate) => ({ role: candidate.role, sourceId: candidate.sourceId, port: candidate.port, host: candidate.host, address: candidate.address, url: candidate.url, metadata: candidate.metadata })) }];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
debugState.lastError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
277
|
+
}
|
|
278
|
+
await sleep(10);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const done = run();
|
|
282
|
+
return {
|
|
283
|
+
async stop() {
|
|
284
|
+
stopped = true;
|
|
285
|
+
await done;
|
|
286
|
+
},
|
|
287
|
+
diagnostics: () => ({ ...debugState }),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
async function startSignalApi(options) {
|
|
291
|
+
const authenticator = new MultiAuthenticator_1.MultiAuthenticator({
|
|
292
|
+
authenticators: [
|
|
293
|
+
new ServiceTokenAuthenticator_1.ServiceTokenAuthenticator({ repository: options.serviceTokenRepo }),
|
|
294
|
+
new NodeTokenAuthenticator_1.NodeTokenAuthenticator({ repository: options.nodeRepo }),
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
const server = new ApiServer_1.ApiServer({
|
|
298
|
+
port: 0,
|
|
299
|
+
host: '127.0.0.1',
|
|
300
|
+
authMiddleware: new AuthMiddleware_1.AuthMiddleware({ authenticator }),
|
|
301
|
+
});
|
|
302
|
+
let baseUrl = 'http://127.0.0.1:0';
|
|
303
|
+
(0, EdgeNodeSignalHandler_1.registerEdgeNodeSignalRoutes)(server, { repository: options.nodeRepo });
|
|
304
|
+
(0, ReachabilityHandler_1.registerReachabilityRoutes)(server, {
|
|
305
|
+
repository: options.nodeRepo,
|
|
306
|
+
baseStorageDomain: options.baseStorageDomain,
|
|
307
|
+
apiBaseUrl: () => baseUrl,
|
|
308
|
+
});
|
|
309
|
+
await server.start();
|
|
310
|
+
const address = server.address();
|
|
311
|
+
if (!address || typeof address === 'string') {
|
|
312
|
+
await server.stop();
|
|
313
|
+
throw new Error('Expected signal API TCP address info');
|
|
314
|
+
}
|
|
315
|
+
baseUrl = `http://127.0.0.1:${address.port}`;
|
|
316
|
+
return {
|
|
317
|
+
baseUrl,
|
|
318
|
+
close: () => server.stop(),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function startTargetServer(options) {
|
|
322
|
+
const requests = [];
|
|
323
|
+
const server = (0, node_http_1.createServer)((request, response) => {
|
|
324
|
+
requests.push({
|
|
325
|
+
method: request.method ?? 'GET',
|
|
326
|
+
url: request.url ?? '/',
|
|
327
|
+
headers: request.headers,
|
|
328
|
+
});
|
|
329
|
+
if (request.url === options.resourcePath) {
|
|
330
|
+
response.statusCode = 200;
|
|
331
|
+
response.setHeader('content-type', 'text/plain');
|
|
332
|
+
response.end(options.targetBody);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
response.statusCode = 404;
|
|
336
|
+
response.end('not found');
|
|
337
|
+
});
|
|
338
|
+
await new Promise((resolve, reject) => {
|
|
339
|
+
server.once('error', reject);
|
|
340
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
341
|
+
});
|
|
342
|
+
const address = server.address();
|
|
343
|
+
if (!address || typeof address === 'string') {
|
|
344
|
+
throw new Error('Expected target HTTP server address info');
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
baseUrl: `http://127.0.0.1:${address.port}/`,
|
|
348
|
+
requests,
|
|
349
|
+
close: () => closeHttpServer(server),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
async function waitForRoute(options) {
|
|
353
|
+
const startedAt = Date.now();
|
|
354
|
+
let lastError = '';
|
|
355
|
+
while (Date.now() - startedAt < options.timeoutMs) {
|
|
356
|
+
try {
|
|
357
|
+
const response = await fetch(new URL(`/v1/signal/nodes/${encodeURIComponent(options.nodeId)}/routes`, options.apiBaseUrl), {
|
|
358
|
+
headers: {
|
|
359
|
+
authorization: `Bearer ${options.token}`,
|
|
360
|
+
accept: 'application/json',
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
if (!response.ok) {
|
|
364
|
+
lastError = `${response.status} ${await response.text()}`;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
const body = await response.json();
|
|
368
|
+
const route = body.routes?.find(options.predicate);
|
|
369
|
+
if (route) {
|
|
370
|
+
return route;
|
|
371
|
+
}
|
|
372
|
+
lastError = JSON.stringify(body);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
377
|
+
}
|
|
378
|
+
await sleep(20);
|
|
379
|
+
}
|
|
380
|
+
throw new Error(`Timed out waiting for P2P route: ${lastError}`);
|
|
381
|
+
}
|
|
382
|
+
async function reserveTcpPort() {
|
|
383
|
+
const server = (0, node_net_1.createServer)();
|
|
384
|
+
await new Promise((resolve, reject) => {
|
|
385
|
+
server.once('error', reject);
|
|
386
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
387
|
+
});
|
|
388
|
+
const address = server.address();
|
|
389
|
+
if (!address || typeof address === 'string') {
|
|
390
|
+
throw new Error('Expected TCP address info');
|
|
391
|
+
}
|
|
392
|
+
await closeTcpServer(server);
|
|
393
|
+
return address.port;
|
|
394
|
+
}
|
|
395
|
+
async function createSocketPair() {
|
|
396
|
+
const server = (0, node_net_1.createServer)();
|
|
397
|
+
const serverSocketPromise = new Promise((resolve) => {
|
|
398
|
+
server.once('connection', resolve);
|
|
399
|
+
});
|
|
400
|
+
await new Promise((resolve, reject) => {
|
|
401
|
+
server.once('error', reject);
|
|
402
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
403
|
+
});
|
|
404
|
+
const address = server.address();
|
|
405
|
+
const clientSocket = await new Promise((resolve, reject) => {
|
|
406
|
+
const socket = (0, node_net_1.createConnection)({ host: '127.0.0.1', port: address.port });
|
|
407
|
+
socket.once('connect', () => resolve(socket));
|
|
408
|
+
socket.once('error', reject);
|
|
409
|
+
});
|
|
410
|
+
const serverSocket = await serverSocketPromise;
|
|
411
|
+
return {
|
|
412
|
+
clientSocket,
|
|
413
|
+
serverSocket,
|
|
414
|
+
async close() {
|
|
415
|
+
clientSocket.destroy();
|
|
416
|
+
serverSocket.destroy();
|
|
417
|
+
await closeTcpServer(server);
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function normalizeResourcePath(value) {
|
|
422
|
+
if (!value.startsWith('/')) {
|
|
423
|
+
return `/${value}`;
|
|
424
|
+
}
|
|
425
|
+
return value;
|
|
426
|
+
}
|
|
427
|
+
function closeHttpServer(server) {
|
|
428
|
+
return new Promise((resolve, reject) => {
|
|
429
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function closeTcpServer(server) {
|
|
433
|
+
return new Promise((resolve, reject) => {
|
|
434
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
function sleep(ms) {
|
|
438
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
439
|
+
}
|
|
440
|
+
function createDeferred() {
|
|
441
|
+
let settled = false;
|
|
442
|
+
let resolve;
|
|
443
|
+
let reject;
|
|
444
|
+
const promise = new Promise((promiseResolve, promiseReject) => {
|
|
445
|
+
resolve = (value) => {
|
|
446
|
+
settled = true;
|
|
447
|
+
promiseResolve(value);
|
|
448
|
+
};
|
|
449
|
+
reject = (reason) => {
|
|
450
|
+
settled = true;
|
|
451
|
+
promiseReject(reason);
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
resolve,
|
|
456
|
+
reject,
|
|
457
|
+
wait: async (timeoutMs, timeoutMessage) => {
|
|
458
|
+
if (settled) {
|
|
459
|
+
return promise;
|
|
460
|
+
}
|
|
461
|
+
let timeout;
|
|
462
|
+
try {
|
|
463
|
+
return await Promise.race([
|
|
464
|
+
promise,
|
|
465
|
+
new Promise((_, timeoutReject) => {
|
|
466
|
+
timeout = setTimeout(() => timeoutReject(new Error(timeoutMessage)), timeoutMs);
|
|
467
|
+
}),
|
|
468
|
+
]);
|
|
469
|
+
}
|
|
470
|
+
finally {
|
|
471
|
+
if (timeout) {
|
|
472
|
+
clearTimeout(timeout);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
//# sourceMappingURL=local-managed-client-p2p-e2e-smoke.js.map
|