@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,388 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerReachabilityRoutes = registerReachabilityRoutes;
|
|
4
|
+
const AuthContext_1 = require("../auth/AuthContext");
|
|
5
|
+
const RouteSetBuilder_1 = require("../../edge/reachability/RouteSetBuilder");
|
|
6
|
+
const ReachabilitySessionService_1 = require("../../edge/reachability/ReachabilitySessionService");
|
|
7
|
+
function registerReachabilityRoutes(server, options) {
|
|
8
|
+
const service = new ReachabilitySessionService_1.ReachabilitySessionService({
|
|
9
|
+
repository: options.repository,
|
|
10
|
+
baseStorageDomain: options.baseStorageDomain,
|
|
11
|
+
apiBaseUrl: options.apiBaseUrl ?? process.env.XPOD_CLOUD_API_ENDPOINT ?? process.env.CSS_BASE_URL ?? 'http://localhost/',
|
|
12
|
+
now: options.now,
|
|
13
|
+
randomId: options.randomId,
|
|
14
|
+
maxActiveP2PSessionsPerNode: options.maxActiveP2PSessionsPerNode
|
|
15
|
+
?? parsePositiveInteger(process.env.XPOD_P2P_MAX_ACTIVE_SESSIONS_PER_NODE),
|
|
16
|
+
maxP2PCandidatesPerUpdate: options.maxP2PCandidatesPerUpdate
|
|
17
|
+
?? parsePositiveInteger(process.env.XPOD_P2P_MAX_CANDIDATES_PER_UPDATE),
|
|
18
|
+
maxP2PCandidatesPerSession: options.maxP2PCandidatesPerSession
|
|
19
|
+
?? parsePositiveInteger(process.env.XPOD_P2P_MAX_CANDIDATES_PER_SESSION),
|
|
20
|
+
});
|
|
21
|
+
server.get('/v1/signal/nodes/:nodeId/routes', async (request, response, params) => {
|
|
22
|
+
const access = resolveAccess(request, params.nodeId);
|
|
23
|
+
if (!access.allowed) {
|
|
24
|
+
sendJson(response, access.status, { error: access.error });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const source = await loadRouteSource(options.repository, params.nodeId, options.baseStorageDomain);
|
|
29
|
+
const routeSet = (0, RouteSetBuilder_1.buildRouteSet)(source, {
|
|
30
|
+
audience: access.audience,
|
|
31
|
+
baseStorageDomain: options.baseStorageDomain,
|
|
32
|
+
now: options.now?.() ?? new Date(),
|
|
33
|
+
});
|
|
34
|
+
sendJson(response, 200, routeSet);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof ReachabilitySessionService_1.NodeRouteSourceNotFoundError) {
|
|
38
|
+
sendJson(response, 404, { error: 'Node not found' });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
sendJson(response, 500, { error: 'Failed to load routes' });
|
|
42
|
+
}
|
|
43
|
+
}, { optionalAuth: true });
|
|
44
|
+
server.post('/v1/signal/nodes/:nodeId/sessions', async (request, response, params) => {
|
|
45
|
+
const access = resolveSessionAccess(request, params.nodeId);
|
|
46
|
+
if (!access.allowed) {
|
|
47
|
+
sendJson(response, access.status, { error: access.error });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const body = await readJsonBody(request);
|
|
51
|
+
if (!isRecord(body)) {
|
|
52
|
+
sendJson(response, 400, { error: 'kind must be p2p or relay' });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (body.kind === 'p2p') {
|
|
56
|
+
if (typeof body.clientId !== 'string' || body.clientId.trim().length === 0) {
|
|
57
|
+
sendJson(response, 400, { error: 'clientId is required' });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const session = await service.createP2PSession(params.nodeId, {
|
|
62
|
+
kind: 'p2p',
|
|
63
|
+
clientId: body.clientId.trim(),
|
|
64
|
+
...(access.kind === 'solid' ? { owner: { type: 'solid', webId: access.webId } } : {}),
|
|
65
|
+
capabilities: Array.isArray(body.capabilities) ? body.capabilities.filter((entry) => typeof entry === 'string') : [],
|
|
66
|
+
candidates: enrichP2PCandidatesWithObservedAddress(Array.isArray(body.candidates) ? body.candidates : [], resolveObservedAddress(request)),
|
|
67
|
+
});
|
|
68
|
+
sendJson(response, 201, session);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof ReachabilitySessionService_1.NodeRouteSourceNotFoundError) {
|
|
72
|
+
sendJson(response, 404, { error: 'Node not found' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (error instanceof ReachabilitySessionService_1.P2PActiveSessionLimitExceededError) {
|
|
76
|
+
sendJson(response, 429, { error: 'P2P active session limit exceeded' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (error instanceof ReachabilitySessionService_1.P2PCandidateUpdateLimitExceededError) {
|
|
80
|
+
sendJson(response, 429, { error: 'P2P candidate update limit exceeded' });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (error instanceof ReachabilitySessionService_1.P2PCandidateSessionLimitExceededError) {
|
|
84
|
+
sendJson(response, 429, { error: 'P2P candidate session limit exceeded' });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
sendJson(response, 500, { error: 'Failed to create p2p session' });
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (body.kind === 'relay') {
|
|
92
|
+
if (typeof body.reason !== 'string' || body.reason.trim().length === 0) {
|
|
93
|
+
sendJson(response, 400, { error: 'reason is required for relay sessions' });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const session = await service.createRelaySession(params.nodeId, {
|
|
98
|
+
kind: 'relay',
|
|
99
|
+
reason: body.reason,
|
|
100
|
+
ttlSeconds: typeof body.ttlSeconds === 'number' ? body.ttlSeconds : undefined,
|
|
101
|
+
bandwidthLimitBytes: typeof body.bandwidthLimitBytes === 'number' ? body.bandwidthLimitBytes : undefined,
|
|
102
|
+
bandwidthLimitBps: typeof body.bandwidthLimitBps === 'number' ? body.bandwidthLimitBps : undefined,
|
|
103
|
+
});
|
|
104
|
+
sendJson(response, 201, session);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof ReachabilitySessionService_1.InvalidRelaySessionRequestError) {
|
|
108
|
+
sendJson(response, 400, { error: error.message });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (error instanceof ReachabilitySessionService_1.NodeRouteSourceNotFoundError) {
|
|
112
|
+
sendJson(response, 404, { error: 'Node not found' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
sendJson(response, 500, { error: 'Failed to create relay session' });
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
sendJson(response, 400, { error: 'kind must be p2p or relay' });
|
|
120
|
+
});
|
|
121
|
+
server.get('/v1/signal/nodes/:nodeId/sessions', async (request, response, params) => {
|
|
122
|
+
const access = resolveSessionAccess(request, params.nodeId);
|
|
123
|
+
if (!access.allowed) {
|
|
124
|
+
sendJson(response, access.status, { error: access.error });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const sessions = await service.listP2PSessions(params.nodeId);
|
|
129
|
+
sendJson(response, 200, filterP2PSessionListForAccess(sessions, access));
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof ReachabilitySessionService_1.NodeRouteSourceNotFoundError) {
|
|
133
|
+
sendJson(response, 404, { error: 'Node not found' });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
sendJson(response, 500, { error: 'Failed to list p2p sessions' });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
server.get('/v1/signal/nodes/:nodeId/sessions/:sessionId', async (request, response, params) => {
|
|
140
|
+
const access = resolveSessionAccess(request, params.nodeId);
|
|
141
|
+
if (!access.allowed) {
|
|
142
|
+
sendJson(response, access.status, { error: access.error });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const session = await service.getP2PSession(params.nodeId, params.sessionId);
|
|
147
|
+
if (!canAccessP2PSession(access, session)) {
|
|
148
|
+
sendJson(response, 403, { error: 'Solid user cannot access another client signaling session' });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
sendJson(response, 200, session);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
sendP2PSessionError(response, error);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
server.post('/v1/signal/nodes/:nodeId/sessions/:sessionId/candidates', async (request, response, params) => {
|
|
158
|
+
const access = resolveSessionAccess(request, params.nodeId);
|
|
159
|
+
if (!access.allowed) {
|
|
160
|
+
sendJson(response, access.status, { error: access.error });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const body = await readJsonBody(request);
|
|
164
|
+
if (!isRecord(body) || !Array.isArray(body.candidates)) {
|
|
165
|
+
sendJson(response, 400, { error: 'candidates array is required' });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const currentSession = await service.getP2PSession(params.nodeId, params.sessionId);
|
|
170
|
+
if (!canAccessP2PSession(access, currentSession)) {
|
|
171
|
+
sendJson(response, 403, { error: 'Solid user cannot access another client signaling session' });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const source = resolveCandidateSource(access, params.nodeId, currentSession, body);
|
|
175
|
+
const session = await service.addP2PCandidates(params.nodeId, params.sessionId, {
|
|
176
|
+
role: source.role,
|
|
177
|
+
sourceId: source.sourceId,
|
|
178
|
+
candidates: enrichP2PCandidatesWithObservedAddress(body.candidates, resolveObservedAddress(request)),
|
|
179
|
+
});
|
|
180
|
+
sendJson(response, 200, session);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
sendP2PSessionError(response, error);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function loadRouteSource(repository, nodeId, baseStorageDomain) {
|
|
188
|
+
const [metadataRow, connectivity] = await Promise.all([
|
|
189
|
+
repository.getNodeMetadata(nodeId),
|
|
190
|
+
repository.getNodeConnectivityInfo(nodeId),
|
|
191
|
+
]);
|
|
192
|
+
if (!metadataRow && !connectivity) {
|
|
193
|
+
throw new ReachabilitySessionService_1.NodeRouteSourceNotFoundError(`Node ${nodeId} not found`);
|
|
194
|
+
}
|
|
195
|
+
const metadata = metadataRow?.metadata ?? {};
|
|
196
|
+
return {
|
|
197
|
+
nodeId,
|
|
198
|
+
canonicalUrl: getString(metadata.canonicalUrl),
|
|
199
|
+
publicUrl: connectivity?.publicUrl,
|
|
200
|
+
subdomain: connectivity?.subdomain,
|
|
201
|
+
baseStorageDomain,
|
|
202
|
+
ipv4: connectivity?.ipv4,
|
|
203
|
+
publicPort: connectivity?.publicPort,
|
|
204
|
+
connectivityStatus: connectivity?.connectivityStatus,
|
|
205
|
+
metadata,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function resolveAccess(request, nodeId) {
|
|
209
|
+
const auth = request.auth;
|
|
210
|
+
if (!auth) {
|
|
211
|
+
return { allowed: true, audience: 'public' };
|
|
212
|
+
}
|
|
213
|
+
if ((0, AuthContext_1.isNodeAuth)(auth)) {
|
|
214
|
+
if (auth.nodeId !== nodeId) {
|
|
215
|
+
return { allowed: false, status: 403, error: 'Node token cannot access another node' };
|
|
216
|
+
}
|
|
217
|
+
return { allowed: true, audience: 'managed' };
|
|
218
|
+
}
|
|
219
|
+
if ((0, AuthContext_1.isServiceAuth)(auth)) {
|
|
220
|
+
return { allowed: true, audience: 'managed' };
|
|
221
|
+
}
|
|
222
|
+
return { allowed: true, audience: 'public' };
|
|
223
|
+
}
|
|
224
|
+
function resolveSessionAccess(request, nodeId) {
|
|
225
|
+
const auth = request.auth;
|
|
226
|
+
if (!auth) {
|
|
227
|
+
return { allowed: false, status: 401, error: 'Authentication required' };
|
|
228
|
+
}
|
|
229
|
+
if ((0, AuthContext_1.isNodeAuth)(auth)) {
|
|
230
|
+
if (auth.nodeId !== nodeId) {
|
|
231
|
+
return { allowed: false, status: 403, error: 'Node token cannot access another node' };
|
|
232
|
+
}
|
|
233
|
+
return { allowed: true, kind: 'node', nodeId: auth.nodeId };
|
|
234
|
+
}
|
|
235
|
+
if ((0, AuthContext_1.isServiceAuth)(auth)) {
|
|
236
|
+
return { allowed: true, kind: 'service' };
|
|
237
|
+
}
|
|
238
|
+
if ((0, AuthContext_1.isSolidAuth)(auth)) {
|
|
239
|
+
return { allowed: true, kind: 'solid', webId: auth.webId };
|
|
240
|
+
}
|
|
241
|
+
return { allowed: false, status: 403, error: 'Unsupported reachability session credentials' };
|
|
242
|
+
}
|
|
243
|
+
function filterP2PSessionListForAccess(sessions, access) {
|
|
244
|
+
if (access.kind !== 'solid') {
|
|
245
|
+
return sessions;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
kind: sessions.kind,
|
|
249
|
+
sessions: sessions.sessions.filter((session) => isOwnedBySolidUser(session, access.webId)),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function canAccessP2PSession(access, session) {
|
|
253
|
+
return access.kind !== 'solid' || isOwnedBySolidUser(session, access.webId);
|
|
254
|
+
}
|
|
255
|
+
function isOwnedBySolidUser(session, webId) {
|
|
256
|
+
return session.owner?.type === 'solid' && session.owner.webId === webId;
|
|
257
|
+
}
|
|
258
|
+
function resolveCandidateSource(access, nodeId, session, body) {
|
|
259
|
+
if (access.kind === 'node') {
|
|
260
|
+
return { role: 'node', sourceId: nodeId };
|
|
261
|
+
}
|
|
262
|
+
if (access.kind === 'solid') {
|
|
263
|
+
return { role: 'client', sourceId: session.clientId };
|
|
264
|
+
}
|
|
265
|
+
const role = body.role === 'node' ? 'node' : 'client';
|
|
266
|
+
const sourceId = getString(body.sourceId)
|
|
267
|
+
?? getString(body.clientId)
|
|
268
|
+
?? (role === 'node' ? nodeId : 'service-client');
|
|
269
|
+
return { role, sourceId };
|
|
270
|
+
}
|
|
271
|
+
function enrichP2PCandidatesWithObservedAddress(candidates, observedAddress) {
|
|
272
|
+
if (!observedAddress) {
|
|
273
|
+
return candidates;
|
|
274
|
+
}
|
|
275
|
+
return candidates.map((candidate) => {
|
|
276
|
+
if (!isRecord(candidate) || !isPortOnlyCandidate(candidate)) {
|
|
277
|
+
return candidate;
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
...candidate,
|
|
281
|
+
address: observedAddress,
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function isPortOnlyCandidate(candidate) {
|
|
286
|
+
return normalizeCandidatePort(candidate.port) !== undefined
|
|
287
|
+
&& getString(candidate.host) === undefined
|
|
288
|
+
&& getString(candidate.address) === undefined
|
|
289
|
+
&& getString(candidate.url) === undefined;
|
|
290
|
+
}
|
|
291
|
+
function normalizeCandidatePort(value) {
|
|
292
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
const port = Math.floor(value);
|
|
296
|
+
return port > 0 && port <= 65535 ? port : undefined;
|
|
297
|
+
}
|
|
298
|
+
function resolveObservedAddress(request) {
|
|
299
|
+
return firstForwardedAddress(request.headers['x-forwarded-for'])
|
|
300
|
+
?? firstHeaderAddress(request.headers['x-real-ip'])
|
|
301
|
+
?? normalizeObservedAddress(request.socket?.remoteAddress);
|
|
302
|
+
}
|
|
303
|
+
function firstForwardedAddress(value) {
|
|
304
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
305
|
+
return firstHeaderAddress(first?.split(',')[0]);
|
|
306
|
+
}
|
|
307
|
+
function firstHeaderAddress(value) {
|
|
308
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
309
|
+
return normalizeObservedAddress(first);
|
|
310
|
+
}
|
|
311
|
+
function normalizeObservedAddress(value) {
|
|
312
|
+
const trimmed = value?.trim().replace(/^"|"$/gu, '');
|
|
313
|
+
if (!trimmed) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
if (trimmed.startsWith('::ffff:')) {
|
|
317
|
+
return trimmed.slice('::ffff:'.length);
|
|
318
|
+
}
|
|
319
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
320
|
+
return trimmed.slice(1, -1);
|
|
321
|
+
}
|
|
322
|
+
return trimmed;
|
|
323
|
+
}
|
|
324
|
+
async function readJsonBody(request) {
|
|
325
|
+
return new Promise((resolve, reject) => {
|
|
326
|
+
let data = '';
|
|
327
|
+
request.setEncoding('utf8');
|
|
328
|
+
request.on('data', (chunk) => {
|
|
329
|
+
data += chunk;
|
|
330
|
+
});
|
|
331
|
+
request.on('end', () => {
|
|
332
|
+
if (!data) {
|
|
333
|
+
resolve(undefined);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
resolve(JSON.parse(data));
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
resolve(undefined);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
request.on('error', reject);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
function sendJson(response, status, data) {
|
|
347
|
+
response.statusCode = status;
|
|
348
|
+
response.setHeader('Content-Type', 'application/json');
|
|
349
|
+
response.end(JSON.stringify(data));
|
|
350
|
+
}
|
|
351
|
+
function sendP2PSessionError(response, error) {
|
|
352
|
+
if (error instanceof ReachabilitySessionService_1.P2PSessionExpiredError) {
|
|
353
|
+
sendJson(response, 410, { error: 'P2P session expired' });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (error instanceof ReachabilitySessionService_1.P2PSessionNotFoundError) {
|
|
357
|
+
sendJson(response, 404, { error: 'P2P session not found' });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (error instanceof ReachabilitySessionService_1.P2PCandidateUpdateLimitExceededError) {
|
|
361
|
+
sendJson(response, 429, { error: 'P2P candidate update limit exceeded' });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (error instanceof ReachabilitySessionService_1.P2PCandidateSessionLimitExceededError) {
|
|
365
|
+
sendJson(response, 429, { error: 'P2P candidate session limit exceeded' });
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (error instanceof ReachabilitySessionService_1.NodeRouteSourceNotFoundError) {
|
|
369
|
+
sendJson(response, 404, { error: 'Node not found' });
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
sendJson(response, 500, { error: 'Failed to update p2p session' });
|
|
373
|
+
}
|
|
374
|
+
function getString(value) {
|
|
375
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
376
|
+
}
|
|
377
|
+
function parsePositiveInteger(value) {
|
|
378
|
+
const trimmed = getString(value);
|
|
379
|
+
if (!trimmed) {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
383
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
384
|
+
}
|
|
385
|
+
function isRecord(value) {
|
|
386
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=ReachabilityHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReachabilityHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ReachabilityHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport type { ApiServer } from '../ApiServer';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { isNodeAuth, isServiceAuth, isSolidAuth } from '../auth/AuthContext';\nimport { buildRouteSet } from '../../edge/reachability/RouteSetBuilder';\nimport {\n InvalidRelaySessionRequestError,\n NodeRouteSourceNotFoundError,\n P2PActiveSessionLimitExceededError,\n P2PCandidateSessionLimitExceededError,\n P2PCandidateUpdateLimitExceededError,\n P2PSessionExpiredError,\n P2PSessionNotFoundError,\n ReachabilitySessionService,\n} from '../../edge/reachability/ReachabilitySessionService';\nimport type { BuildRouteSetSource, P2PCandidateRole, P2PSession, RouteAudience } from '../../edge/reachability/types';\n\nexport interface ReachabilityHandlerOptions {\n repository: EdgeNodeRepository;\n baseStorageDomain?: string;\n apiBaseUrl?: string | (() => string);\n now?: () => Date;\n randomId?: () => string;\n maxActiveP2PSessionsPerNode?: number;\n maxP2PCandidatesPerUpdate?: number;\n maxP2PCandidatesPerSession?: number;\n}\n\nexport function registerReachabilityRoutes(server: ApiServer, options: ReachabilityHandlerOptions): void {\n const service = new ReachabilitySessionService({\n repository: options.repository,\n baseStorageDomain: options.baseStorageDomain,\n apiBaseUrl: options.apiBaseUrl ?? process.env.XPOD_CLOUD_API_ENDPOINT ?? process.env.CSS_BASE_URL ?? 'http://localhost/',\n now: options.now,\n randomId: options.randomId,\n maxActiveP2PSessionsPerNode: options.maxActiveP2PSessionsPerNode\n ?? parsePositiveInteger(process.env.XPOD_P2P_MAX_ACTIVE_SESSIONS_PER_NODE),\n maxP2PCandidatesPerUpdate: options.maxP2PCandidatesPerUpdate\n ?? parsePositiveInteger(process.env.XPOD_P2P_MAX_CANDIDATES_PER_UPDATE),\n maxP2PCandidatesPerSession: options.maxP2PCandidatesPerSession\n ?? parsePositiveInteger(process.env.XPOD_P2P_MAX_CANDIDATES_PER_SESSION),\n });\n\n server.get('/v1/signal/nodes/:nodeId/routes', async (request, response, params) => {\n const access = resolveAccess(request, params.nodeId);\n if (!access.allowed) {\n sendJson(response, access.status, { error: access.error });\n return;\n }\n\n try {\n const source = await loadRouteSource(options.repository, params.nodeId, options.baseStorageDomain);\n const routeSet = buildRouteSet(source, {\n audience: access.audience,\n baseStorageDomain: options.baseStorageDomain,\n now: options.now?.() ?? new Date(),\n });\n sendJson(response, 200, routeSet);\n } catch (error) {\n if (error instanceof NodeRouteSourceNotFoundError) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n sendJson(response, 500, { error: 'Failed to load routes' });\n }\n }, { optionalAuth: true });\n\n server.post('/v1/signal/nodes/:nodeId/sessions', async (request, response, params) => {\n const access = resolveSessionAccess(request, params.nodeId);\n if (!access.allowed) {\n sendJson(response, access.status, { error: access.error });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!isRecord(body)) {\n sendJson(response, 400, { error: 'kind must be p2p or relay' });\n return;\n }\n\n if (body.kind === 'p2p') {\n if (typeof body.clientId !== 'string' || body.clientId.trim().length === 0) {\n sendJson(response, 400, { error: 'clientId is required' });\n return;\n }\n try {\n const session = await service.createP2PSession(params.nodeId, {\n kind: 'p2p',\n clientId: body.clientId.trim(),\n ...(access.kind === 'solid' ? { owner: { type: 'solid' as const, webId: access.webId } } : {}),\n capabilities: Array.isArray(body.capabilities) ? body.capabilities.filter((entry): entry is string => typeof entry === 'string') : [],\n candidates: enrichP2PCandidatesWithObservedAddress(\n Array.isArray(body.candidates) ? body.candidates : [],\n resolveObservedAddress(request),\n ),\n });\n sendJson(response, 201, session);\n } catch (error) {\n if (error instanceof NodeRouteSourceNotFoundError) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n if (error instanceof P2PActiveSessionLimitExceededError) {\n sendJson(response, 429, { error: 'P2P active session limit exceeded' });\n return;\n }\n if (error instanceof P2PCandidateUpdateLimitExceededError) {\n sendJson(response, 429, { error: 'P2P candidate update limit exceeded' });\n return;\n }\n if (error instanceof P2PCandidateSessionLimitExceededError) {\n sendJson(response, 429, { error: 'P2P candidate session limit exceeded' });\n return;\n }\n sendJson(response, 500, { error: 'Failed to create p2p session' });\n }\n return;\n }\n\n if (body.kind === 'relay') {\n if (typeof body.reason !== 'string' || body.reason.trim().length === 0) {\n sendJson(response, 400, { error: 'reason is required for relay sessions' });\n return;\n }\n try {\n const session = await service.createRelaySession(params.nodeId, {\n kind: 'relay',\n reason: body.reason,\n ttlSeconds: typeof body.ttlSeconds === 'number' ? body.ttlSeconds : undefined,\n bandwidthLimitBytes: typeof body.bandwidthLimitBytes === 'number' ? body.bandwidthLimitBytes : undefined,\n bandwidthLimitBps: typeof body.bandwidthLimitBps === 'number' ? body.bandwidthLimitBps : undefined,\n });\n sendJson(response, 201, session);\n } catch (error) {\n if (error instanceof InvalidRelaySessionRequestError) {\n sendJson(response, 400, { error: error.message });\n return;\n }\n if (error instanceof NodeRouteSourceNotFoundError) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n sendJson(response, 500, { error: 'Failed to create relay session' });\n }\n return;\n }\n\n sendJson(response, 400, { error: 'kind must be p2p or relay' });\n });\n\n server.get('/v1/signal/nodes/:nodeId/sessions', async (request, response, params) => {\n const access = resolveSessionAccess(request, params.nodeId);\n if (!access.allowed) {\n sendJson(response, access.status, { error: access.error });\n return;\n }\n\n try {\n const sessions = await service.listP2PSessions(params.nodeId);\n sendJson(response, 200, filterP2PSessionListForAccess(sessions, access));\n } catch (error) {\n if (error instanceof NodeRouteSourceNotFoundError) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n sendJson(response, 500, { error: 'Failed to list p2p sessions' });\n }\n });\n\n server.get('/v1/signal/nodes/:nodeId/sessions/:sessionId', async (request, response, params) => {\n const access = resolveSessionAccess(request, params.nodeId);\n if (!access.allowed) {\n sendJson(response, access.status, { error: access.error });\n return;\n }\n\n try {\n const session = await service.getP2PSession(params.nodeId, params.sessionId);\n if (!canAccessP2PSession(access, session)) {\n sendJson(response, 403, { error: 'Solid user cannot access another client signaling session' });\n return;\n }\n sendJson(response, 200, session);\n } catch (error) {\n sendP2PSessionError(response, error);\n }\n });\n\n server.post('/v1/signal/nodes/:nodeId/sessions/:sessionId/candidates', async (request, response, params) => {\n const access = resolveSessionAccess(request, params.nodeId);\n if (!access.allowed) {\n sendJson(response, access.status, { error: access.error });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!isRecord(body) || !Array.isArray(body.candidates)) {\n sendJson(response, 400, { error: 'candidates array is required' });\n return;\n }\n\n try {\n const currentSession = await service.getP2PSession(params.nodeId, params.sessionId);\n if (!canAccessP2PSession(access, currentSession)) {\n sendJson(response, 403, { error: 'Solid user cannot access another client signaling session' });\n return;\n }\n const source = resolveCandidateSource(access, params.nodeId, currentSession, body);\n const session = await service.addP2PCandidates(params.nodeId, params.sessionId, {\n role: source.role,\n sourceId: source.sourceId,\n candidates: enrichP2PCandidatesWithObservedAddress(body.candidates, resolveObservedAddress(request)),\n });\n sendJson(response, 200, session);\n } catch (error) {\n sendP2PSessionError(response, error);\n }\n });\n}\n\nasync function loadRouteSource(\n repository: EdgeNodeRepository,\n nodeId: string,\n baseStorageDomain?: string,\n): Promise<BuildRouteSetSource> {\n const [metadataRow, connectivity] = await Promise.all([\n repository.getNodeMetadata(nodeId),\n repository.getNodeConnectivityInfo(nodeId),\n ]);\n if (!metadataRow && !connectivity) {\n throw new NodeRouteSourceNotFoundError(`Node ${nodeId} not found`);\n }\n const metadata = metadataRow?.metadata ?? {};\n return {\n nodeId,\n canonicalUrl: getString(metadata.canonicalUrl),\n publicUrl: connectivity?.publicUrl,\n subdomain: connectivity?.subdomain,\n baseStorageDomain,\n ipv4: connectivity?.ipv4,\n publicPort: connectivity?.publicPort,\n connectivityStatus: connectivity?.connectivityStatus,\n metadata,\n };\n}\n\nfunction resolveAccess(request: AuthenticatedRequest, nodeId: string):\n | { allowed: true; audience: RouteAudience }\n | { allowed: false; status: number; error: string } {\n const auth = request.auth;\n if (!auth) {\n return { allowed: true, audience: 'public' };\n }\n if (isNodeAuth(auth)) {\n if (auth.nodeId !== nodeId) {\n return { allowed: false, status: 403, error: 'Node token cannot access another node' };\n }\n return { allowed: true, audience: 'managed' };\n }\n if (isServiceAuth(auth)) {\n return { allowed: true, audience: 'managed' };\n }\n return { allowed: true, audience: 'public' };\n}\n\nfunction resolveSessionAccess(request: AuthenticatedRequest, nodeId: string):\n | { allowed: true; kind: 'node'; nodeId: string }\n | { allowed: true; kind: 'service' }\n | { allowed: true; kind: 'solid'; webId: string }\n | { allowed: false; status: number; error: string } {\n const auth = request.auth;\n if (!auth) {\n return { allowed: false, status: 401, error: 'Authentication required' };\n }\n if (isNodeAuth(auth)) {\n if (auth.nodeId !== nodeId) {\n return { allowed: false, status: 403, error: 'Node token cannot access another node' };\n }\n return { allowed: true, kind: 'node', nodeId: auth.nodeId };\n }\n if (isServiceAuth(auth)) {\n return { allowed: true, kind: 'service' };\n }\n if (isSolidAuth(auth)) {\n return { allowed: true, kind: 'solid', webId: auth.webId };\n }\n return { allowed: false, status: 403, error: 'Unsupported reachability session credentials' };\n}\n\nfunction filterP2PSessionListForAccess(\n sessions: { kind: 'p2p'; sessions: P2PSession[] },\n access: { kind: 'node'; nodeId: string } | { kind: 'service' } | { kind: 'solid'; webId: string },\n): { kind: 'p2p'; sessions: P2PSession[] } {\n if (access.kind !== 'solid') {\n return sessions;\n }\n return {\n kind: sessions.kind,\n sessions: sessions.sessions.filter((session) => isOwnedBySolidUser(session, access.webId)),\n };\n}\n\nfunction canAccessP2PSession(\n access: { kind: 'node'; nodeId: string } | { kind: 'service' } | { kind: 'solid'; webId: string },\n session: P2PSession,\n): boolean {\n return access.kind !== 'solid' || isOwnedBySolidUser(session, access.webId);\n}\n\nfunction isOwnedBySolidUser(session: P2PSession, webId: string): boolean {\n return session.owner?.type === 'solid' && session.owner.webId === webId;\n}\n\nfunction resolveCandidateSource(\n access: { kind: 'node'; nodeId: string } | { kind: 'service' } | { kind: 'solid'; webId: string },\n nodeId: string,\n session: P2PSession,\n body: Record<string, unknown>,\n): { role: P2PCandidateRole; sourceId: string } {\n if (access.kind === 'node') {\n return { role: 'node', sourceId: nodeId };\n }\n if (access.kind === 'solid') {\n return { role: 'client', sourceId: session.clientId };\n }\n\n const role: P2PCandidateRole = body.role === 'node' ? 'node' : 'client';\n const sourceId = getString(body.sourceId)\n ?? getString(body.clientId)\n ?? (role === 'node' ? nodeId : 'service-client');\n return { role, sourceId };\n}\n\nfunction enrichP2PCandidatesWithObservedAddress(candidates: unknown[], observedAddress: string | undefined): unknown[] {\n if (!observedAddress) {\n return candidates;\n }\n\n return candidates.map((candidate) => {\n if (!isRecord(candidate) || !isPortOnlyCandidate(candidate)) {\n return candidate;\n }\n return {\n ...candidate,\n address: observedAddress,\n };\n });\n}\n\nfunction isPortOnlyCandidate(candidate: Record<string, unknown>): boolean {\n return normalizeCandidatePort(candidate.port) !== undefined\n && getString(candidate.host) === undefined\n && getString(candidate.address) === undefined\n && getString(candidate.url) === undefined;\n}\n\nfunction normalizeCandidatePort(value: unknown): number | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return undefined;\n }\n const port = Math.floor(value);\n return port > 0 && port <= 65535 ? port : undefined;\n}\n\nfunction resolveObservedAddress(request: AuthenticatedRequest): string | undefined {\n return firstForwardedAddress(request.headers['x-forwarded-for'])\n ?? firstHeaderAddress(request.headers['x-real-ip'])\n ?? normalizeObservedAddress(request.socket?.remoteAddress);\n}\n\nfunction firstForwardedAddress(value: string | string[] | undefined): string | undefined {\n const first = Array.isArray(value) ? value[0] : value;\n return firstHeaderAddress(first?.split(',')[0]);\n}\n\nfunction firstHeaderAddress(value: string | string[] | undefined): string | undefined {\n const first = Array.isArray(value) ? value[0] : value;\n return normalizeObservedAddress(first);\n}\n\nfunction normalizeObservedAddress(value: string | undefined): string | undefined {\n const trimmed = value?.trim().replace(/^\"|\"$/gu, '');\n if (!trimmed) {\n return undefined;\n }\n if (trimmed.startsWith('::ffff:')) {\n return trimmed.slice('::ffff:'.length);\n }\n if (trimmed.startsWith('[') && trimmed.endsWith(']')) {\n return trimmed.slice(1, -1);\n }\n return trimmed;\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction sendP2PSessionError(response: ServerResponse, error: unknown): void {\n if (error instanceof P2PSessionExpiredError) {\n sendJson(response, 410, { error: 'P2P session expired' });\n return;\n }\n if (error instanceof P2PSessionNotFoundError) {\n sendJson(response, 404, { error: 'P2P session not found' });\n return;\n }\n if (error instanceof P2PCandidateUpdateLimitExceededError) {\n sendJson(response, 429, { error: 'P2P candidate update limit exceeded' });\n return;\n }\n if (error instanceof P2PCandidateSessionLimitExceededError) {\n sendJson(response, 429, { error: 'P2P candidate session limit exceeded' });\n return;\n }\n if (error instanceof NodeRouteSourceNotFoundError) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n sendJson(response, 500, { error: 'Failed to update p2p session' });\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;\n}\n\nfunction parsePositiveInteger(value: string | undefined): number | undefined {\n const trimmed = getString(value);\n if (!trimmed) {\n return undefined;\n }\n const parsed = Number.parseInt(trimmed, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n"]}
|
|
@@ -147,7 +147,7 @@ export declare class InngestRunExecutionBackend implements RunExecutionBackend {
|
|
|
147
147
|
readonly event: "xpod/run.continue_requested";
|
|
148
148
|
}], Omit<Omit<import("inngest").BaseContext<Inngest.Any>, never> & Record<never, never> & {
|
|
149
149
|
logger: import("inngest").Logger;
|
|
150
|
-
}, "group" | "requestId" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "
|
|
150
|
+
}, "group" | "requestId" | "attempt" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "maxAttempts"> & import("inngest").FailureEventArgs<EventPayload<any>> & {
|
|
151
151
|
step: {
|
|
152
152
|
sendEvent: (idOrOptions: import("inngest").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<Inngest<import("inngest").ClientOptions>>>>;
|
|
153
153
|
waitForSignal: <TData>(idOrOptions: import("inngest").StepOptionsOrId, opts: {
|
|
@@ -318,7 +318,7 @@ export declare class InngestRunExecutionBackend implements RunExecutionBackend {
|
|
|
318
318
|
readonly event: "xpod/run.continue_requested";
|
|
319
319
|
}], Omit<Omit<import("inngest").BaseContext<Inngest.Any>, never> & Record<never, never> & {
|
|
320
320
|
logger: import("inngest").Logger;
|
|
321
|
-
}, "group" | "requestId" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "
|
|
321
|
+
}, "group" | "requestId" | "attempt" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "maxAttempts"> & import("inngest").FailureEventArgs<EventPayload<any>> & {
|
|
322
322
|
step: {
|
|
323
323
|
sendEvent: (idOrOptions: import("inngest").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<Inngest<import("inngest").ClientOptions>>>>;
|
|
324
324
|
waitForSignal: <TData>(idOrOptions: import("inngest").StepOptionsOrId, opts: {
|
|
@@ -128,7 +128,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
128
128
|
readonly event: "xpod/task.materialize_due";
|
|
129
129
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
130
130
|
logger: import("inngest").Logger;
|
|
131
|
-
}, "group" | "requestId" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "
|
|
131
|
+
}, "group" | "requestId" | "attempt" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
132
132
|
step: {
|
|
133
133
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
134
134
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -296,7 +296,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
296
296
|
readonly event: "xpod/task.materialize_due";
|
|
297
297
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
298
298
|
logger: import("inngest").Logger;
|
|
299
|
-
}, "group" | "requestId" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "
|
|
299
|
+
}, "group" | "requestId" | "attempt" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
300
300
|
step: {
|
|
301
301
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
302
302
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -469,7 +469,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
469
469
|
readonly event: "xpod/task.event";
|
|
470
470
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
471
471
|
logger: import("inngest").Logger;
|
|
472
|
-
}, "group" | "requestId" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "
|
|
472
|
+
}, "group" | "requestId" | "attempt" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
473
473
|
step: {
|
|
474
474
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
475
475
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -635,7 +635,7 @@ export declare class InngestTaskScheduler<TContext = StoreContext> {
|
|
|
635
635
|
readonly event: "xpod/task.event";
|
|
636
636
|
}], Omit<Omit<import("inngest/types").BaseContext<import("inngest").Inngest.Any>, never> & Record<never, never> & {
|
|
637
637
|
logger: import("inngest").Logger;
|
|
638
|
-
}, "group" | "requestId" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "
|
|
638
|
+
}, "group" | "requestId" | "attempt" | "event" | "runId" | "step" | "defer" | "events" | "jobId" | "maxAttempts"> & import("inngest/types").FailureEventArgs<EventPayload<any>> & {
|
|
639
639
|
step: {
|
|
640
640
|
sendEvent: (idOrOptions: import("inngest/types").StepOptionsOrId, payload: import("inngest").SendEventPayload) => Promise<import("inngest/types").SendEventOutput<import("inngest").ClientOptionsFromInngest<import("inngest").Inngest<import("inngest/types").ClientOptions>>>>;
|
|
641
641
|
waitForSignal: <TData>(idOrOptions: import("inngest/types").StepOptionsOrId, opts: {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"undefineds:dist/storage/rdf/PostgresRdfEngine.jsonld",
|
|
15
15
|
"undefineds:dist/dns/DnsProvider.jsonld",
|
|
16
16
|
"undefineds:dist/edge/EdgeNodeCertificateProvisioner.jsonld",
|
|
17
|
+
"undefineds:dist/edge/EdgeNodeAgent.jsonld",
|
|
17
18
|
"undefineds:dist/edge/interfaces/EdgeNodeTunnelManager.jsonld",
|
|
18
19
|
"undefineds:dist/quota/QuotaService.jsonld",
|
|
19
20
|
"undefineds:dist/quota/EntitlementProvider.jsonld",
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
"undefineds:dist/edge/EdgeNodeTunnelManager.jsonld",
|
|
57
58
|
"undefineds:dist/edge/FrpTunnelManager.jsonld",
|
|
58
59
|
"undefineds:dist/edge/EdgeNodeHealthProbeService.jsonld",
|
|
59
|
-
"undefineds:dist/edge/
|
|
60
|
+
"undefineds:dist/edge/EdgeNodeAgentInitializer.jsonld",
|
|
60
61
|
"undefineds:dist/service/EdgeNodeCertificateService.jsonld",
|
|
61
62
|
"undefineds:dist/edge/acme/AcmeCertificateManager.jsonld",
|
|
62
63
|
"undefineds:dist/edge/EdgeNodeModeDetector.jsonld",
|
|
@@ -99,6 +100,17 @@
|
|
|
99
100
|
"undefineds:dist/api/reconciler/coordination.jsonld",
|
|
100
101
|
"undefineds:dist/api/reconciler/ClientReconcilerCoordinator.jsonld",
|
|
101
102
|
"undefineds:dist/api/reconciler/WakeAgentQueue.jsonld",
|
|
102
|
-
"undefineds:dist/api/reconciler/ServerGroupReconcilerService.jsonld"
|
|
103
|
+
"undefineds:dist/api/reconciler/ServerGroupReconcilerService.jsonld",
|
|
104
|
+
"undefineds:dist/edge/reachability/types.jsonld",
|
|
105
|
+
"undefineds:dist/edge/reachability/ReachabilitySessionService.jsonld",
|
|
106
|
+
"undefineds:dist/edge/reachability/ManagedRouteSelector.jsonld",
|
|
107
|
+
"undefineds:dist/edge/reachability/CanonicalFetch.jsonld",
|
|
108
|
+
"undefineds:dist/edge/reachability/P2PDataPlane.jsonld",
|
|
109
|
+
"undefineds:dist/edge/reachability/P2PSignalingClient.jsonld",
|
|
110
|
+
"undefineds:dist/edge/reachability/TcpP2PDataPlaneTransport.jsonld",
|
|
111
|
+
"undefineds:dist/edge/reachability/TcpP2PSignalingSession.jsonld",
|
|
112
|
+
"undefineds:dist/edge/reachability/ManagedClientFetch.jsonld",
|
|
113
|
+
"undefineds:dist/edge/reachability/ManagedClientP2PSmoke.jsonld",
|
|
114
|
+
"undefineds:dist/edge/reachability/P2PRealnetAcceptance.jsonld"
|
|
103
115
|
]
|
|
104
116
|
}
|