@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.
Files changed (117) hide show
  1. package/config/cli.json +72 -0
  2. package/config/components-ignore.json +21 -0
  3. package/config/extensions.local.initializer.json +77 -8
  4. package/config/resolver.json +72 -4
  5. package/dist/api/ApiServer.d.ts +12 -0
  6. package/dist/api/ApiServer.js +14 -3
  7. package/dist/api/ApiServer.js.map +1 -1
  8. package/dist/api/auth/NodeTokenAuthenticator.d.ts +0 -8
  9. package/dist/api/auth/NodeTokenAuthenticator.js +3 -46
  10. package/dist/api/auth/NodeTokenAuthenticator.js.map +1 -1
  11. package/dist/api/container/local.js +5 -36
  12. package/dist/api/container/local.js.map +1 -1
  13. package/dist/api/container/routes.js +6 -0
  14. package/dist/api/container/routes.js.map +1 -1
  15. package/dist/api/handlers/EdgeNodeSignalHandler.js +25 -3
  16. package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -1
  17. package/dist/api/handlers/ReachabilityHandler.d.ts +13 -0
  18. package/dist/api/handlers/ReachabilityHandler.js +388 -0
  19. package/dist/api/handlers/ReachabilityHandler.js.map +1 -0
  20. package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
  21. package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
  22. package/dist/components/components.jsonld +14 -2
  23. package/dist/components/context.jsonld +439 -3
  24. package/dist/edge/EdgeNodeAgent.d.ts +43 -0
  25. package/dist/edge/EdgeNodeAgent.js +208 -1
  26. package/dist/edge/EdgeNodeAgent.js.map +1 -1
  27. package/dist/edge/EdgeNodeAgent.jsonld +166 -0
  28. package/dist/edge/EdgeNodeAgentInitializer.d.ts +20 -2
  29. package/dist/edge/EdgeNodeAgentInitializer.js +53 -2
  30. package/dist/edge/EdgeNodeAgentInitializer.js.map +1 -1
  31. package/dist/edge/EdgeNodeAgentInitializer.jsonld +409 -0
  32. package/dist/edge/reachability/CanonicalFetch.d.ts +7 -0
  33. package/dist/edge/reachability/CanonicalFetch.js +49 -0
  34. package/dist/edge/reachability/CanonicalFetch.js.map +1 -0
  35. package/dist/edge/reachability/CanonicalFetch.jsonld +25 -0
  36. package/dist/edge/reachability/ManagedClientFetch.d.ts +26 -0
  37. package/dist/edge/reachability/ManagedClientFetch.js +155 -0
  38. package/dist/edge/reachability/ManagedClientFetch.js.map +1 -0
  39. package/dist/edge/reachability/ManagedClientFetch.jsonld +83 -0
  40. package/dist/edge/reachability/ManagedClientP2PSmoke.d.ts +16 -0
  41. package/dist/edge/reachability/ManagedClientP2PSmoke.js +31 -0
  42. package/dist/edge/reachability/ManagedClientP2PSmoke.js.map +1 -0
  43. package/dist/edge/reachability/ManagedClientP2PSmoke.jsonld +65 -0
  44. package/dist/edge/reachability/ManagedRouteSelector.d.ts +7 -0
  45. package/dist/edge/reachability/ManagedRouteSelector.js +43 -0
  46. package/dist/edge/reachability/ManagedRouteSelector.js.map +1 -0
  47. package/dist/edge/reachability/ManagedRouteSelector.jsonld +29 -0
  48. package/dist/edge/reachability/P2PDataPlane.d.ts +37 -0
  49. package/dist/edge/reachability/P2PDataPlane.js +160 -0
  50. package/dist/edge/reachability/P2PDataPlane.js.map +1 -0
  51. package/dist/edge/reachability/P2PDataPlane.jsonld +134 -0
  52. package/dist/edge/reachability/P2PRealnetAcceptance.d.ts +74 -0
  53. package/dist/edge/reachability/P2PRealnetAcceptance.js +240 -0
  54. package/dist/edge/reachability/P2PRealnetAcceptance.js.map +1 -0
  55. package/dist/edge/reachability/P2PRealnetAcceptance.jsonld +283 -0
  56. package/dist/edge/reachability/P2PSignalingClient.d.ts +24 -0
  57. package/dist/edge/reachability/P2PSignalingClient.js +138 -0
  58. package/dist/edge/reachability/P2PSignalingClient.js.map +1 -0
  59. package/dist/edge/reachability/P2PSignalingClient.jsonld +79 -0
  60. package/dist/edge/reachability/ReachabilitySessionService.d.ts +55 -0
  61. package/dist/edge/reachability/ReachabilitySessionService.js +439 -0
  62. package/dist/edge/reachability/ReachabilitySessionService.js.map +1 -0
  63. package/dist/edge/reachability/ReachabilitySessionService.jsonld +196 -0
  64. package/dist/edge/reachability/RouteSetBuilder.d.ts +2 -0
  65. package/dist/edge/reachability/RouteSetBuilder.js +205 -0
  66. package/dist/edge/reachability/RouteSetBuilder.js.map +1 -0
  67. package/dist/edge/reachability/TcpP2PDataPlaneTransport.d.ts +47 -0
  68. package/dist/edge/reachability/TcpP2PDataPlaneTransport.js +281 -0
  69. package/dist/edge/reachability/TcpP2PDataPlaneTransport.js.map +1 -0
  70. package/dist/edge/reachability/TcpP2PDataPlaneTransport.jsonld +183 -0
  71. package/dist/edge/reachability/TcpP2PSignalingSession.d.ts +149 -0
  72. package/dist/edge/reachability/TcpP2PSignalingSession.js +699 -0
  73. package/dist/edge/reachability/TcpP2PSignalingSession.js.map +1 -0
  74. package/dist/edge/reachability/TcpP2PSignalingSession.jsonld +474 -0
  75. package/dist/edge/reachability/index.d.ts +12 -0
  76. package/dist/edge/reachability/index.js +29 -0
  77. package/dist/edge/reachability/index.js.map +1 -0
  78. package/dist/edge/reachability/types.d.ts +114 -0
  79. package/dist/edge/reachability/types.js +3 -0
  80. package/dist/edge/reachability/types.js.map +1 -0
  81. package/dist/edge/reachability/types.jsonld +457 -0
  82. package/dist/http/EdgeNodeProxyHttpHandler.d.ts +2 -0
  83. package/dist/http/EdgeNodeProxyHttpHandler.js +19 -1
  84. package/dist/http/EdgeNodeProxyHttpHandler.js.map +1 -1
  85. package/dist/http/EdgeNodeProxyHttpHandler.jsonld +8 -0
  86. package/dist/identity/drizzle/EdgeNodeRepository.js +1 -1
  87. package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
  88. package/dist/index.d.ts +4 -1
  89. package/dist/index.js +5 -2
  90. package/dist/index.js.map +1 -1
  91. package/dist/runtime/bootstrap.js +8 -0
  92. package/dist/runtime/bootstrap.js.map +1 -1
  93. package/dist/service/EdgeNodeSignalClient.js +5 -1
  94. package/dist/service/EdgeNodeSignalClient.js.map +1 -1
  95. package/dist/storage/rdf/PostgresRdfEngine.d.ts +1 -0
  96. package/dist/storage/rdf/PostgresRdfEngine.js +53 -37
  97. package/dist/storage/rdf/PostgresRdfEngine.js.map +1 -1
  98. package/dist/storage/rdf/PostgresRdfEngine.jsonld +4 -0
  99. package/dist/test-utils/index.d.ts +2 -0
  100. package/dist/test-utils/index.js +3 -1
  101. package/dist/test-utils/index.js.map +1 -1
  102. package/dist/test-utils/local-managed-client-p2p-e2e-smoke.d.ts +63 -0
  103. package/dist/test-utils/local-managed-client-p2p-e2e-smoke.js +478 -0
  104. package/dist/test-utils/local-managed-client-p2p-e2e-smoke.js.map +1 -0
  105. package/package.json +11 -4
  106. package/static/app/assets/_commonjsHelpers-B-UnjaXt.js +1 -0
  107. package/static/app/assets/index-AaQ1qxhy.js +171 -0
  108. package/static/app/assets/inrupt-smoke.js +131 -0
  109. package/static/app/assets/main.css +1 -0
  110. package/static/app/assets/main.js +6 -6
  111. package/static/app/index.html +2 -1
  112. package/static/app/inrupt-smoke.html +14 -0
  113. package/static/app/reachability.html +221 -0
  114. package/static/app/reachability.svg +7 -0
  115. package/static/app/reachability.webmanifest +18 -0
  116. package/static/app/signal-pod.html +293 -0
  117. package/static/app/assets/index.css +0 -1
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.XPOD_P2P_HTTP_PROTOCOL = void 0;
4
+ exports.createP2PDataPlaneFetch = createP2PDataPlaneFetch;
5
+ exports.createP2PDataPlaneHandler = createP2PDataPlaneHandler;
6
+ exports.XPOD_P2P_HTTP_PROTOCOL = 'xpod-p2p-http/1';
7
+ function createP2PDataPlaneFetch(options) {
8
+ if (options.route.kind !== 'p2p') {
9
+ throw new Error(`P2P data plane requires a p2p route, got ${options.route.kind}`);
10
+ }
11
+ const canonicalOrigin = new URL(options.route.canonicalUrl).origin;
12
+ return async (input, init) => {
13
+ const request = new Request(input, init);
14
+ const canonicalUrl = new URL(request.url);
15
+ if (canonicalUrl.origin !== canonicalOrigin) {
16
+ throw new Error(`Request ${canonicalUrl.toString()} is outside canonical origin ${canonicalOrigin}`);
17
+ }
18
+ const frame = {
19
+ protocol: exports.XPOD_P2P_HTTP_PROTOCOL,
20
+ method: request.method,
21
+ url: canonicalUrl.toString(),
22
+ headers: headersToList(request.headers),
23
+ bodyBase64: await bodyToBase64(request),
24
+ };
25
+ const responseFrame = await options.transport.request(frame);
26
+ validateResponseFrame(responseFrame);
27
+ return new Response(base64ToBody(responseFrame.bodyBase64), {
28
+ status: responseFrame.status,
29
+ statusText: responseFrame.statusText,
30
+ headers: new Headers(responseFrame.headers),
31
+ });
32
+ };
33
+ }
34
+ function createP2PDataPlaneHandler(options) {
35
+ const targetBaseUrl = new URL(options.targetBaseUrl.toString());
36
+ const fetchImpl = options.fetchImpl ?? fetch;
37
+ return {
38
+ async handleRequest(frame) {
39
+ validateRequestFrame(frame);
40
+ const canonicalUrl = new URL(frame.url);
41
+ const targetUrl = rewriteTargetUrl(targetBaseUrl, canonicalUrl);
42
+ const headers = new Headers(frame.headers);
43
+ removeHopByHopHeaders(headers);
44
+ headers.set('x-xpod-canonical-url', canonicalUrl.toString());
45
+ headers.set('x-xpod-canonical-origin', canonicalUrl.origin);
46
+ headers.set('x-xpod-canonical-host', canonicalUrl.host);
47
+ const response = await fetchImpl(targetUrl, {
48
+ method: frame.method,
49
+ headers,
50
+ body: methodCanHaveBody(frame.method) ? base64ToBody(frame.bodyBase64) : undefined,
51
+ });
52
+ return {
53
+ protocol: exports.XPOD_P2P_HTTP_PROTOCOL,
54
+ requestId: frame.requestId,
55
+ status: response.status,
56
+ statusText: response.statusText,
57
+ headers: headersToList(response.headers),
58
+ bodyBase64: await responseToBase64(response),
59
+ };
60
+ },
61
+ };
62
+ }
63
+ function validateRequestFrame(frame) {
64
+ if (frame.protocol !== exports.XPOD_P2P_HTTP_PROTOCOL) {
65
+ throw new Error(`Unsupported P2P HTTP protocol: ${String(frame.protocol)}`);
66
+ }
67
+ if (typeof frame.method !== 'string' || frame.method.trim().length === 0) {
68
+ throw new Error('P2P HTTP request method is required');
69
+ }
70
+ try {
71
+ new URL(frame.url);
72
+ }
73
+ catch {
74
+ throw new Error('P2P HTTP request URL must be absolute');
75
+ }
76
+ }
77
+ function validateResponseFrame(frame) {
78
+ if (frame.protocol !== exports.XPOD_P2P_HTTP_PROTOCOL) {
79
+ throw new Error(`Unsupported P2P HTTP protocol: ${String(frame.protocol)}`);
80
+ }
81
+ if (!Number.isInteger(frame.status) || frame.status < 100 || frame.status > 599) {
82
+ throw new Error(`Invalid P2P HTTP response status: ${String(frame.status)}`);
83
+ }
84
+ }
85
+ function rewriteTargetUrl(targetBase, canonicalUrl) {
86
+ const basePath = targetBase.pathname.endsWith('/')
87
+ ? targetBase.pathname.slice(0, -1)
88
+ : targetBase.pathname;
89
+ const targetPath = `${basePath}${canonicalUrl.pathname}`.replace(/\/+/gu, '/');
90
+ const target = new URL(targetBase.toString());
91
+ target.pathname = targetPath;
92
+ target.search = canonicalUrl.search;
93
+ target.hash = '';
94
+ return target.toString();
95
+ }
96
+ function headersToList(headers) {
97
+ const result = [];
98
+ headers.forEach((value, key) => {
99
+ if (!isHopByHopHeader(key)) {
100
+ result.push([key, value]);
101
+ }
102
+ });
103
+ return result;
104
+ }
105
+ function removeHopByHopHeaders(headers) {
106
+ const keysToDelete = [];
107
+ headers.forEach((_value, key) => {
108
+ if (isHopByHopHeader(key)) {
109
+ keysToDelete.push(key);
110
+ }
111
+ });
112
+ for (const key of keysToDelete) {
113
+ headers.delete(key);
114
+ }
115
+ }
116
+ function isHopByHopHeader(key) {
117
+ switch (key.toLowerCase()) {
118
+ case 'connection':
119
+ case 'keep-alive':
120
+ case 'proxy-authenticate':
121
+ case 'proxy-authorization':
122
+ case 'te':
123
+ case 'trailer':
124
+ case 'transfer-encoding':
125
+ case 'upgrade':
126
+ case 'host':
127
+ case 'content-length':
128
+ return true;
129
+ default:
130
+ return false;
131
+ }
132
+ }
133
+ function methodCanHaveBody(method) {
134
+ const normalized = method.toUpperCase();
135
+ return normalized !== 'GET' && normalized !== 'HEAD';
136
+ }
137
+ async function bodyToBase64(request) {
138
+ if (!methodCanHaveBody(request.method)) {
139
+ return undefined;
140
+ }
141
+ const body = await request.arrayBuffer();
142
+ if (body.byteLength === 0) {
143
+ return undefined;
144
+ }
145
+ return Buffer.from(body).toString('base64');
146
+ }
147
+ async function responseToBase64(response) {
148
+ const body = await response.arrayBuffer();
149
+ if (body.byteLength === 0) {
150
+ return undefined;
151
+ }
152
+ return Buffer.from(body).toString('base64');
153
+ }
154
+ function base64ToBody(value) {
155
+ if (!value) {
156
+ return undefined;
157
+ }
158
+ return Buffer.from(value, 'base64');
159
+ }
160
+ //# sourceMappingURL=P2PDataPlane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"P2PDataPlane.js","sourceRoot":"","sources":["../../../src/edge/reachability/P2PDataPlane.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessRoute } from './types';\n\nexport const XPOD_P2P_HTTP_PROTOCOL = 'xpod-p2p-http/1' as const;\n\nexport type P2PHttpProtocol = typeof XPOD_P2P_HTTP_PROTOCOL;\nexport type P2PHttpHeaderList = [string, string][];\n\nexport interface P2PHttpRequestFrame {\n protocol: P2PHttpProtocol;\n requestId?: string;\n method: string;\n url: string;\n headers?: P2PHttpHeaderList;\n bodyBase64?: string;\n}\n\nexport interface P2PHttpResponseFrame {\n protocol: P2PHttpProtocol;\n requestId?: string;\n status: number;\n statusText?: string;\n headers?: P2PHttpHeaderList;\n bodyBase64?: string;\n}\n\nexport interface P2PDataPlaneTransport {\n request(frame: P2PHttpRequestFrame): Promise<P2PHttpResponseFrame>;\n}\n\nexport interface P2PDataPlaneFetchOptions {\n route: AccessRoute;\n transport: P2PDataPlaneTransport;\n}\n\nexport interface P2PDataPlaneHandlerOptions {\n targetBaseUrl: string | URL;\n fetchImpl?: typeof fetch;\n}\n\nexport interface P2PDataPlaneHandler {\n handleRequest(frame: P2PHttpRequestFrame): Promise<P2PHttpResponseFrame>;\n}\n\nexport type P2PDataPlaneFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\nexport function createP2PDataPlaneFetch(options: P2PDataPlaneFetchOptions): P2PDataPlaneFetch {\n if (options.route.kind !== 'p2p') {\n throw new Error(`P2P data plane requires a p2p route, got ${options.route.kind}`);\n }\n const canonicalOrigin = new URL(options.route.canonicalUrl).origin;\n\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = new Request(input, init);\n const canonicalUrl = new URL(request.url);\n if (canonicalUrl.origin !== canonicalOrigin) {\n throw new Error(`Request ${canonicalUrl.toString()} is outside canonical origin ${canonicalOrigin}`);\n }\n\n const frame: P2PHttpRequestFrame = {\n protocol: XPOD_P2P_HTTP_PROTOCOL,\n method: request.method,\n url: canonicalUrl.toString(),\n headers: headersToList(request.headers),\n bodyBase64: await bodyToBase64(request),\n };\n const responseFrame = await options.transport.request(frame);\n validateResponseFrame(responseFrame);\n return new Response(base64ToBody(responseFrame.bodyBase64), {\n status: responseFrame.status,\n statusText: responseFrame.statusText,\n headers: new Headers(responseFrame.headers),\n });\n };\n}\n\nexport function createP2PDataPlaneHandler(options: P2PDataPlaneHandlerOptions): P2PDataPlaneHandler {\n const targetBaseUrl = new URL(options.targetBaseUrl.toString());\n const fetchImpl = options.fetchImpl ?? fetch;\n\n return {\n async handleRequest(frame: P2PHttpRequestFrame): Promise<P2PHttpResponseFrame> {\n validateRequestFrame(frame);\n const canonicalUrl = new URL(frame.url);\n const targetUrl = rewriteTargetUrl(targetBaseUrl, canonicalUrl);\n const headers = new Headers(frame.headers);\n removeHopByHopHeaders(headers);\n headers.set('x-xpod-canonical-url', canonicalUrl.toString());\n headers.set('x-xpod-canonical-origin', canonicalUrl.origin);\n headers.set('x-xpod-canonical-host', canonicalUrl.host);\n\n const response = await fetchImpl(targetUrl, {\n method: frame.method,\n headers,\n body: methodCanHaveBody(frame.method) ? base64ToBody(frame.bodyBase64) : undefined,\n });\n\n return {\n protocol: XPOD_P2P_HTTP_PROTOCOL,\n requestId: frame.requestId,\n status: response.status,\n statusText: response.statusText,\n headers: headersToList(response.headers),\n bodyBase64: await responseToBase64(response),\n };\n },\n };\n}\n\nfunction validateRequestFrame(frame: P2PHttpRequestFrame): void {\n if (frame.protocol !== XPOD_P2P_HTTP_PROTOCOL) {\n throw new Error(`Unsupported P2P HTTP protocol: ${String(frame.protocol)}`);\n }\n if (typeof frame.method !== 'string' || frame.method.trim().length === 0) {\n throw new Error('P2P HTTP request method is required');\n }\n try {\n new URL(frame.url);\n } catch {\n throw new Error('P2P HTTP request URL must be absolute');\n }\n}\n\nfunction validateResponseFrame(frame: P2PHttpResponseFrame): void {\n if (frame.protocol !== XPOD_P2P_HTTP_PROTOCOL) {\n throw new Error(`Unsupported P2P HTTP protocol: ${String(frame.protocol)}`);\n }\n if (!Number.isInteger(frame.status) || frame.status < 100 || frame.status > 599) {\n throw new Error(`Invalid P2P HTTP response status: ${String(frame.status)}`);\n }\n}\n\nfunction rewriteTargetUrl(targetBase: URL, canonicalUrl: URL): string {\n const basePath = targetBase.pathname.endsWith('/')\n ? targetBase.pathname.slice(0, -1)\n : targetBase.pathname;\n const targetPath = `${basePath}${canonicalUrl.pathname}`.replace(/\\/+/gu, '/');\n const target = new URL(targetBase.toString());\n target.pathname = targetPath;\n target.search = canonicalUrl.search;\n target.hash = '';\n return target.toString();\n}\n\nfunction headersToList(headers: Headers): P2PHttpHeaderList {\n const result: P2PHttpHeaderList = [];\n headers.forEach((value, key) => {\n if (!isHopByHopHeader(key)) {\n result.push([key, value]);\n }\n });\n return result;\n}\n\nfunction removeHopByHopHeaders(headers: Headers): void {\n const keysToDelete: string[] = [];\n headers.forEach((_value, key) => {\n if (isHopByHopHeader(key)) {\n keysToDelete.push(key);\n }\n });\n for (const key of keysToDelete) {\n headers.delete(key);\n }\n}\n\nfunction isHopByHopHeader(key: string): boolean {\n switch (key.toLowerCase()) {\n case 'connection':\n case 'keep-alive':\n case 'proxy-authenticate':\n case 'proxy-authorization':\n case 'te':\n case 'trailer':\n case 'transfer-encoding':\n case 'upgrade':\n case 'host':\n case 'content-length':\n return true;\n default:\n return false;\n }\n}\n\nfunction methodCanHaveBody(method: string): boolean {\n const normalized = method.toUpperCase();\n return normalized !== 'GET' && normalized !== 'HEAD';\n}\n\nasync function bodyToBase64(request: Request): Promise<string | undefined> {\n if (!methodCanHaveBody(request.method)) {\n return undefined;\n }\n const body = await request.arrayBuffer();\n if (body.byteLength === 0) {\n return undefined;\n }\n return Buffer.from(body).toString('base64');\n}\n\nasync function responseToBase64(response: Response): Promise<string | undefined> {\n const body = await response.arrayBuffer();\n if (body.byteLength === 0) {\n return undefined;\n }\n return Buffer.from(body).toString('base64');\n}\n\nfunction base64ToBody(value?: string): BodyInit | undefined {\n if (!value) {\n return undefined;\n }\n return Buffer.from(value, 'base64');\n}\n"]}
@@ -0,0 +1,134 @@
1
+ {
2
+ "@context": [
3
+ "https://linkedsoftwaredependencies.org/bundles/npm/@undefineds.co/xpod/^0.0.0/components/context.jsonld"
4
+ ],
5
+ "@id": "npmd:@undefineds.co/xpod",
6
+ "components": [
7
+ {
8
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpRequestFrame",
9
+ "@type": "AbstractClass",
10
+ "requireElement": "P2PHttpRequestFrame",
11
+ "parameters": [],
12
+ "memberFields": [
13
+ {
14
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpRequestFrame__member_protocol",
15
+ "memberFieldName": "protocol"
16
+ },
17
+ {
18
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpRequestFrame__member_requestId",
19
+ "memberFieldName": "requestId"
20
+ },
21
+ {
22
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpRequestFrame__member_method",
23
+ "memberFieldName": "method"
24
+ },
25
+ {
26
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpRequestFrame__member_url",
27
+ "memberFieldName": "url"
28
+ },
29
+ {
30
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpRequestFrame__member_headers",
31
+ "memberFieldName": "headers"
32
+ },
33
+ {
34
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpRequestFrame__member_bodyBase64",
35
+ "memberFieldName": "bodyBase64"
36
+ }
37
+ ],
38
+ "constructorArguments": []
39
+ },
40
+ {
41
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpResponseFrame",
42
+ "@type": "AbstractClass",
43
+ "requireElement": "P2PHttpResponseFrame",
44
+ "parameters": [],
45
+ "memberFields": [
46
+ {
47
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpResponseFrame__member_protocol",
48
+ "memberFieldName": "protocol"
49
+ },
50
+ {
51
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpResponseFrame__member_requestId",
52
+ "memberFieldName": "requestId"
53
+ },
54
+ {
55
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpResponseFrame__member_status",
56
+ "memberFieldName": "status"
57
+ },
58
+ {
59
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpResponseFrame__member_statusText",
60
+ "memberFieldName": "statusText"
61
+ },
62
+ {
63
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpResponseFrame__member_headers",
64
+ "memberFieldName": "headers"
65
+ },
66
+ {
67
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PHttpResponseFrame__member_bodyBase64",
68
+ "memberFieldName": "bodyBase64"
69
+ }
70
+ ],
71
+ "constructorArguments": []
72
+ },
73
+ {
74
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneTransport",
75
+ "@type": "AbstractClass",
76
+ "requireElement": "P2PDataPlaneTransport",
77
+ "parameters": [],
78
+ "memberFields": [
79
+ {
80
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneTransport__member_request",
81
+ "memberFieldName": "request"
82
+ }
83
+ ],
84
+ "constructorArguments": []
85
+ },
86
+ {
87
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneFetchOptions",
88
+ "@type": "AbstractClass",
89
+ "requireElement": "P2PDataPlaneFetchOptions",
90
+ "parameters": [],
91
+ "memberFields": [
92
+ {
93
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneFetchOptions__member_route",
94
+ "memberFieldName": "route"
95
+ },
96
+ {
97
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneFetchOptions__member_transport",
98
+ "memberFieldName": "transport"
99
+ }
100
+ ],
101
+ "constructorArguments": []
102
+ },
103
+ {
104
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneHandlerOptions",
105
+ "@type": "AbstractClass",
106
+ "requireElement": "P2PDataPlaneHandlerOptions",
107
+ "parameters": [],
108
+ "memberFields": [
109
+ {
110
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneHandlerOptions__member_targetBaseUrl",
111
+ "memberFieldName": "targetBaseUrl"
112
+ },
113
+ {
114
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneHandlerOptions__member_fetchImpl",
115
+ "memberFieldName": "fetchImpl"
116
+ }
117
+ ],
118
+ "constructorArguments": []
119
+ },
120
+ {
121
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneHandler",
122
+ "@type": "AbstractClass",
123
+ "requireElement": "P2PDataPlaneHandler",
124
+ "parameters": [],
125
+ "memberFields": [
126
+ {
127
+ "@id": "undefineds:dist/edge/reachability/P2PDataPlane.jsonld#P2PDataPlaneHandler__member_handleRequest",
128
+ "memberFieldName": "handleRequest"
129
+ }
130
+ ],
131
+ "constructorArguments": []
132
+ }
133
+ ]
134
+ }
@@ -0,0 +1,74 @@
1
+ export interface P2PRealnetAcceptancePlanOptions {
2
+ apiBaseUrl: string;
3
+ nodeId: string;
4
+ nodeToken: string;
5
+ baseUrl: string;
6
+ targetBaseUrl: string;
7
+ nodeHost?: string;
8
+ nodeAddress?: string;
9
+ nodeLocalAddress?: string;
10
+ clientHost?: string;
11
+ clientAddress?: string;
12
+ clientLocalAddress?: string;
13
+ clientId: string;
14
+ token?: string;
15
+ resourceUrl: string;
16
+ runTimeoutMs?: number;
17
+ connectTimeoutMs?: number;
18
+ waitTimeoutMs?: number;
19
+ requestTimeoutMs?: number;
20
+ pollIntervalMs?: number;
21
+ winnerSelectionWindowMs?: number;
22
+ windowSeconds?: number;
23
+ maxClockErrorSeconds?: number;
24
+ minRunWindowSeconds?: number;
25
+ numPorts?: number;
26
+ basePort?: number;
27
+ portRange?: number;
28
+ }
29
+ export interface P2PRealnetAcceptanceCommand {
30
+ role: 'node' | 'client';
31
+ command: string[];
32
+ shell: string;
33
+ description: string;
34
+ }
35
+ export interface P2PRealnetAcceptanceMobilePlan {
36
+ appLabel: 'LinX P2P Smoke';
37
+ packageName: 'com.linxmobile.p2psmoke';
38
+ fields: {
39
+ idpUrl: string;
40
+ storageUrl: string;
41
+ clientId: string;
42
+ resourcePath: string;
43
+ };
44
+ description: string;
45
+ }
46
+ export interface P2PRealnetAcceptancePlan {
47
+ kind: 'raw-tcp-p2p-realnet-acceptance';
48
+ node: P2PRealnetAcceptanceCommand;
49
+ client: P2PRealnetAcceptanceCommand;
50
+ mobile: P2PRealnetAcceptanceMobilePlan;
51
+ successCriteria: string[];
52
+ routeFallbacksPreserved: string[];
53
+ caveats: string[];
54
+ }
55
+ export interface P2PRealnetAcceptanceVerifyOptions {
56
+ clientId: string;
57
+ expectedStatus?: number;
58
+ expectedPutStatus?: number;
59
+ requirePutStatus2xx?: boolean;
60
+ nodeResult: unknown;
61
+ clientResult: unknown;
62
+ }
63
+ export interface P2PRealnetAcceptanceCheck {
64
+ name: string;
65
+ ok: boolean;
66
+ detail: string;
67
+ }
68
+ export interface P2PRealnetAcceptanceVerification {
69
+ smokeOk: boolean;
70
+ checks: P2PRealnetAcceptanceCheck[];
71
+ routeFallbacksPreserved: string[];
72
+ }
73
+ export declare function createP2PRealnetAcceptancePlan(options: P2PRealnetAcceptancePlanOptions): P2PRealnetAcceptancePlan;
74
+ export declare function verifyP2PRealnetAcceptance(options: P2PRealnetAcceptanceVerifyOptions): P2PRealnetAcceptanceVerification;
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createP2PRealnetAcceptancePlan = createP2PRealnetAcceptancePlan;
4
+ exports.verifyP2PRealnetAcceptance = verifyP2PRealnetAcceptance;
5
+ const FALLBACKS = ['Cloudflare Tunnel', 'FRP/SakuraFRP'];
6
+ function createP2PRealnetAcceptancePlan(options) {
7
+ const apiBaseUrl = ensureTrailingSlash(options.apiBaseUrl);
8
+ const signalEndpoint = new URL('/v1/signal', apiBaseUrl).toString();
9
+ const nodeCommand = compactCommand([
10
+ 'bun',
11
+ 'run',
12
+ 'smoke:p2p:node-accept',
13
+ '--signal-endpoint',
14
+ signalEndpoint,
15
+ '--node-id',
16
+ options.nodeId,
17
+ '--node-token',
18
+ options.nodeToken,
19
+ '--base-url',
20
+ options.baseUrl,
21
+ '--target-base-url',
22
+ options.targetBaseUrl,
23
+ ...optionalPair('--host', options.nodeHost),
24
+ ...optionalPair('--address', options.nodeAddress),
25
+ ...optionalPair('--local-address', options.nodeLocalAddress),
26
+ ...optionalNumberPair('--connect-timeout-ms', options.connectTimeoutMs),
27
+ ...optionalNumberPair('--winner-selection-window-ms', options.winnerSelectionWindowMs),
28
+ ...optionalNumberPair('--run-timeout-ms', options.runTimeoutMs),
29
+ '--require-accept',
30
+ ]);
31
+ const clientCommand = compactCommand([
32
+ 'bun',
33
+ 'run',
34
+ 'smoke:p2p:managed',
35
+ '--api-base-url',
36
+ apiBaseUrl,
37
+ '--node-id',
38
+ options.nodeId,
39
+ '--client-id',
40
+ options.clientId,
41
+ ...optionalPair('--token', options.token),
42
+ ...optionalPair('--host', options.clientHost),
43
+ ...optionalPair('--address', options.clientAddress),
44
+ ...optionalPair('--local-address', options.clientLocalAddress),
45
+ '--resource-url',
46
+ options.resourceUrl,
47
+ ...optionalNumberPair('--connect-timeout-ms', options.connectTimeoutMs),
48
+ ...optionalNumberPair('--winner-selection-window-ms', options.winnerSelectionWindowMs),
49
+ ...optionalNumberPair('--wait-timeout-ms', options.waitTimeoutMs),
50
+ ...optionalNumberPair('--poll-interval-ms', options.pollIntervalMs),
51
+ ...optionalNumberPair('--request-timeout-ms', options.requestTimeoutMs),
52
+ ...optionalNumberPair('--window-seconds', options.windowSeconds),
53
+ ...optionalNumberPair('--max-clock-error-seconds', options.maxClockErrorSeconds),
54
+ ...optionalNumberPair('--min-run-window-seconds', options.minRunWindowSeconds),
55
+ ...optionalNumberPair('--num-ports', options.numPorts),
56
+ ...optionalNumberPair('--base-port', options.basePort),
57
+ ...optionalNumberPair('--port-range', options.portRange),
58
+ '--require-p2p',
59
+ ]);
60
+ return {
61
+ kind: 'raw-tcp-p2p-realnet-acceptance',
62
+ node: {
63
+ role: 'node',
64
+ command: nodeCommand,
65
+ shell: shellQuoteCommand(nodeCommand),
66
+ description: 'Run this on the local node/SP machine that can reach the local CSS/SP target base URL.',
67
+ },
68
+ client: {
69
+ role: 'client',
70
+ command: clientCommand,
71
+ shell: shellQuoteCommand(clientCommand),
72
+ description: 'Run this from a second non-browser runtime on another network/device.',
73
+ },
74
+ mobile: createMobilePlan(options),
75
+ successCriteria: [
76
+ `node accepted at least one raw TCP P2P session for client ${options.clientId}`,
77
+ 'client selected route.kind=p2p and route.id=p2p-raw-tcp',
78
+ 'client emitted a raw TCP connector success event',
79
+ 'client evidence reports clientAddress=signal-observed',
80
+ 'node accepted evidence for the same client reports nodeAddress=signal-observed',
81
+ 'canonical Solid HTTP request returned the expected status over xpod-p2p-http/1',
82
+ 'mobile write/read validation may require both putStatus and status to match expected 2xx values',
83
+ 'both peers may omit host/address and rely on signal-observed address enrichment for port-only candidates',
84
+ 'Cloudflare Tunnel and FRP/SakuraFRP remain available as independent user-tunnel fallback routes',
85
+ ],
86
+ routeFallbacksPreserved: [...FALLBACKS],
87
+ caveats: [
88
+ 'This is for non-browser runtimes only; it does not prove browser P2P because browsers cannot open raw TCP sockets.',
89
+ 'A successful loopback/local smoke is not enough; run the node and client commands from separate network contexts for cross-NAT evidence.',
90
+ 'When host/address is omitted, signal injects the observed address for port-only candidates; observed address discovery does not guarantee NAT reachability.',
91
+ 'Mobile smoke evidence must still be verified with the same realnet verifier; phone USB is only for install/log collection, not the data plane.',
92
+ 'Cloudflare Tunnel and FRP/SakuraFRP remain fallback routes and are not replaced by raw TCP P2P.',
93
+ ],
94
+ };
95
+ }
96
+ function verifyP2PRealnetAcceptance(options) {
97
+ const node = asRecord(options.nodeResult);
98
+ const client = asRecord(options.clientResult);
99
+ const accepted = Array.isArray(node.accepted) ? node.accepted.filter(asRecord) : [];
100
+ const route = asRecord(client.route);
101
+ const connectorEvents = Array.isArray(client.connectorEvents) ? client.connectorEvents.filter(asRecord) : [];
102
+ const routeFallbacksPreserved = fallbackEvidence(node);
103
+ const checks = [
104
+ {
105
+ name: 'node smoke ok',
106
+ ok: node.smokeOk === true,
107
+ detail: node.smokeOk === true ? 'node runner reported smokeOk=true' : 'node runner did not report smokeOk=true',
108
+ },
109
+ {
110
+ name: 'node accepted client',
111
+ ok: accepted.some((event) => event.clientId === options.clientId),
112
+ detail: accepted.length > 0
113
+ ? `accepted clients: ${accepted.map((event) => String(event.clientId ?? '<unknown>')).join(', ')}`
114
+ : 'node runner accepted no sessions',
115
+ },
116
+ {
117
+ name: 'client smoke ok',
118
+ ok: client.smokeOk === true,
119
+ detail: client.smokeOk === true ? 'client runner reported smokeOk=true' : 'client runner did not report smokeOk=true',
120
+ },
121
+ {
122
+ name: 'client selected p2p route',
123
+ ok: route.kind === 'p2p',
124
+ detail: `client route.kind=${String(route.kind ?? '<missing>')}`,
125
+ },
126
+ {
127
+ name: 'raw tcp connector succeeded',
128
+ ok: connectorEvents.some((event) => event.type === 'success'),
129
+ detail: connectorEvents.length > 0
130
+ ? `connector events: ${connectorEvents.map((event) => String(event.type ?? '<unknown>')).join(', ')}`
131
+ : 'client runner emitted no connector events',
132
+ },
133
+ {
134
+ name: 'client address came from signal',
135
+ ok: client.clientAddress === 'signal-observed',
136
+ detail: `clientAddress=${String(client.clientAddress ?? '<missing>')}`,
137
+ },
138
+ {
139
+ name: 'node address came from signal',
140
+ ok: accepted.some((event) => event.clientId === options.clientId && event.nodeAddress === 'signal-observed'),
141
+ detail: accepted.length > 0
142
+ ? `accepted nodeAddress evidence: ${accepted.map((event) => `${String(event.clientId ?? '<unknown>')}=${String(event.nodeAddress ?? '<missing>')}`).join(', ')}`
143
+ : 'node runner accepted no sessions',
144
+ },
145
+ {
146
+ name: 'expected http status',
147
+ ok: options.expectedStatus === undefined || client.status === options.expectedStatus,
148
+ detail: options.expectedStatus === undefined
149
+ ? 'no expected HTTP status was configured'
150
+ : `client status=${String(client.status ?? '<missing>')}, expected=${options.expectedStatus}`,
151
+ },
152
+ {
153
+ name: 'expected write http status',
154
+ ok: options.expectedPutStatus === undefined || client.putStatus === options.expectedPutStatus,
155
+ detail: options.expectedPutStatus === undefined
156
+ ? 'no expected write HTTP status was configured'
157
+ : `client putStatus=${String(client.putStatus ?? '<missing>')}, expected=${options.expectedPutStatus}`,
158
+ },
159
+ {
160
+ name: 'write http status is 2xx',
161
+ ok: options.requirePutStatus2xx !== true || is2xxStatus(client.putStatus),
162
+ detail: options.requirePutStatus2xx !== true
163
+ ? '2xx write HTTP status was not required'
164
+ : `client putStatus=${String(client.putStatus ?? '<missing>')}`,
165
+ },
166
+ {
167
+ name: 'tunnel fallbacks preserved',
168
+ ok: FALLBACKS.every((fallback) => routeFallbacksPreserved.includes(fallback)),
169
+ detail: `fallback evidence: ${routeFallbacksPreserved.join(', ') || '<none>'}`,
170
+ },
171
+ ];
172
+ return {
173
+ smokeOk: checks.every((check) => check.ok),
174
+ checks,
175
+ routeFallbacksPreserved,
176
+ };
177
+ }
178
+ function createMobilePlan(options) {
179
+ const resource = new URL(options.resourceUrl);
180
+ const storageUrl = `${resource.origin}/`;
181
+ return {
182
+ appLabel: 'LinX P2P Smoke',
183
+ packageName: 'com.linxmobile.p2psmoke',
184
+ fields: {
185
+ idpUrl: deriveIdpBaseUrlFromApiBaseUrl(options.apiBaseUrl),
186
+ storageUrl,
187
+ clientId: options.clientId,
188
+ resourcePath: resource.pathname,
189
+ },
190
+ description: 'Install/run the mobile smoke package over USB/ADB/HDB/Xcode, log in on-device, and use the same clientId as the node acceptance command. The app obtains auth from its own client session; no signal token is a human input.',
191
+ };
192
+ }
193
+ function deriveIdpBaseUrlFromApiBaseUrl(apiBaseUrl) {
194
+ const api = new URL(ensureTrailingSlash(apiBaseUrl));
195
+ const labels = api.hostname.split('.');
196
+ if (labels[0] === 'api' && labels.length > 1) {
197
+ labels[0] = 'id';
198
+ }
199
+ return `${api.protocol}//${labels.join('.')}/`;
200
+ }
201
+ function optionalPair(name, value) {
202
+ return value === undefined || value.length === 0 ? [] : [name, value];
203
+ }
204
+ function optionalNumberPair(name, value) {
205
+ return value === undefined ? [] : [name, String(value)];
206
+ }
207
+ function compactCommand(parts) {
208
+ return parts.filter((part) => part.length > 0);
209
+ }
210
+ function shellQuoteCommand(command) {
211
+ return command.map(shellQuote).join(' ');
212
+ }
213
+ function shellQuote(value) {
214
+ if (/^[A-Za-z0-9_./:=@+-]+$/u.test(value)) {
215
+ return value;
216
+ }
217
+ return `'${value.replace(/'/gu, `'\\''`)}'`;
218
+ }
219
+ function ensureTrailingSlash(value) {
220
+ return value.endsWith('/') ? value : `${value}/`;
221
+ }
222
+ function asRecord(value) {
223
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
224
+ }
225
+ function is2xxStatus(value) {
226
+ return typeof value === 'number' && Number.isInteger(value) && value >= 200 && value <= 299;
227
+ }
228
+ function fallbackEvidence(node) {
229
+ const explicit = Array.isArray(node.routeFallbacksPreserved)
230
+ ? node.routeFallbacksPreserved.filter((value) => typeof value === 'string')
231
+ : [];
232
+ const caveats = Array.isArray(node.caveats)
233
+ ? node.caveats.filter((value) => typeof value === 'string').join('\n')
234
+ : '';
235
+ return [...new Set([
236
+ ...explicit,
237
+ ...FALLBACKS.filter((fallback) => caveats.includes(fallback)),
238
+ ])];
239
+ }
240
+ //# sourceMappingURL=P2PRealnetAcceptance.js.map