@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,55 @@
1
+ import type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';
2
+ import type { P2PCandidateUpdateRequest, P2PSession, P2PSessionList, P2PSessionRequest, RelaySession, RelaySessionRequest } from './types';
3
+ export interface ReachabilitySessionServiceOptions {
4
+ repository: EdgeNodeRepository;
5
+ baseStorageDomain?: string;
6
+ apiBaseUrl: string | (() => string);
7
+ now?: () => Date;
8
+ randomId?: () => string;
9
+ defaultP2PTtlSeconds?: number;
10
+ defaultRelayTtlSeconds?: number;
11
+ defaultRelayBandwidthLimitBytes?: number;
12
+ maxActiveP2PSessionsPerNode?: number;
13
+ maxP2PCandidatesPerUpdate?: number;
14
+ maxP2PCandidatesPerSession?: number;
15
+ }
16
+ export declare class ReachabilitySessionService {
17
+ private readonly options;
18
+ private readonly now;
19
+ private readonly randomId;
20
+ private readonly defaultP2PTtlSeconds;
21
+ private readonly defaultRelayTtlSeconds;
22
+ private readonly defaultRelayBandwidthLimitBytes;
23
+ private readonly maxActiveP2PSessionsPerNode;
24
+ private readonly maxP2PCandidatesPerUpdate;
25
+ private readonly maxP2PCandidatesPerSession;
26
+ constructor(options: ReachabilitySessionServiceOptions);
27
+ createP2PSession(nodeId: string, request: P2PSessionRequest): Promise<P2PSession>;
28
+ getP2PSession(nodeId: string, sessionId: string): Promise<P2PSession>;
29
+ listP2PSessions(nodeId: string): Promise<P2PSessionList>;
30
+ addP2PCandidates(nodeId: string, sessionId: string, request: P2PCandidateUpdateRequest): Promise<P2PSession>;
31
+ createRelaySession(nodeId: string, request: RelaySessionRequest): Promise<RelaySession>;
32
+ private loadNodeRouteSource;
33
+ private readActiveP2PSessions;
34
+ private p2pSessionLimits;
35
+ private resolveP2PSessionLimits;
36
+ private appendSession;
37
+ private loadP2PSession;
38
+ private assertP2PSessionActive;
39
+ private isP2PSessionActive;
40
+ private normalizeP2PCandidates;
41
+ }
42
+ export declare class InvalidRelaySessionRequestError extends Error {
43
+ }
44
+ export declare class NodeRouteSourceNotFoundError extends Error {
45
+ }
46
+ export declare class P2PActiveSessionLimitExceededError extends Error {
47
+ }
48
+ export declare class P2PCandidateUpdateLimitExceededError extends Error {
49
+ }
50
+ export declare class P2PCandidateSessionLimitExceededError extends Error {
51
+ }
52
+ export declare class P2PSessionExpiredError extends Error {
53
+ }
54
+ export declare class P2PSessionNotFoundError extends Error {
55
+ }
@@ -0,0 +1,439 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.P2PSessionNotFoundError = exports.P2PSessionExpiredError = exports.P2PCandidateSessionLimitExceededError = exports.P2PCandidateUpdateLimitExceededError = exports.P2PActiveSessionLimitExceededError = exports.NodeRouteSourceNotFoundError = exports.InvalidRelaySessionRequestError = exports.ReachabilitySessionService = void 0;
4
+ const RouteSetBuilder_1 = require("./RouteSetBuilder");
5
+ class ReachabilitySessionService {
6
+ constructor(options) {
7
+ this.options = options;
8
+ this.now = options.now ?? (() => new Date());
9
+ this.randomId = options.randomId ?? randomString;
10
+ this.defaultP2PTtlSeconds = options.defaultP2PTtlSeconds ?? 5 * 60;
11
+ this.defaultRelayTtlSeconds = options.defaultRelayTtlSeconds ?? 15 * 60;
12
+ this.defaultRelayBandwidthLimitBytes = options.defaultRelayBandwidthLimitBytes ?? 64 * 1024 * 1024;
13
+ this.maxActiveP2PSessionsPerNode = options.maxActiveP2PSessionsPerNode ?? 16;
14
+ this.maxP2PCandidatesPerUpdate = options.maxP2PCandidatesPerUpdate ?? 32;
15
+ this.maxP2PCandidatesPerSession = options.maxP2PCandidatesPerSession ?? 256;
16
+ }
17
+ async createP2PSession(nodeId, request) {
18
+ const source = await this.loadNodeRouteSource(nodeId);
19
+ const activeSessions = this.readActiveP2PSessions(source.metadata);
20
+ if (activeSessions.length >= this.maxActiveP2PSessionsPerNode) {
21
+ throw new P2PActiveSessionLimitExceededError(`Node ${nodeId} reached active P2P session limit`);
22
+ }
23
+ const createdAt = this.now();
24
+ const expiresAt = addSeconds(createdAt, this.defaultP2PTtlSeconds);
25
+ const suffix = this.randomId();
26
+ const sessionId = `p2p_${suffix}`;
27
+ const auditId = `audit_${suffix}`;
28
+ const limits = this.p2pSessionLimits();
29
+ if (Array.isArray(request.candidates) && request.candidates.length > limits.maxCandidatesPerUpdate) {
30
+ throw new P2PCandidateUpdateLimitExceededError('P2P candidate update limit exceeded');
31
+ }
32
+ const candidates = this.normalizeP2PCandidates(request.candidates, {
33
+ role: 'client',
34
+ sourceId: request.clientId,
35
+ createdAt,
36
+ });
37
+ if (candidates.length > limits.maxCandidatesTotal) {
38
+ throw new P2PCandidateSessionLimitExceededError('P2P candidate session limit exceeded');
39
+ }
40
+ const routeSet = (0, RouteSetBuilder_1.buildRouteSet)(source, {
41
+ audience: 'managed',
42
+ baseStorageDomain: this.options.baseStorageDomain,
43
+ now: createdAt,
44
+ });
45
+ const session = {
46
+ sessionId,
47
+ kind: 'p2p',
48
+ nodeId,
49
+ clientId: request.clientId,
50
+ ...(request.owner ? { owner: request.owner } : {}),
51
+ auditId,
52
+ createdAt: createdAt.toISOString(),
53
+ expiresAt: expiresAt.toISOString(),
54
+ nodeCandidates: routeSet.routes,
55
+ signalingUrl: new URL(`/v1/signal/nodes/${encodeURIComponent(nodeId)}/sessions/${sessionId}`, resolveApiBaseUrl(this.options.apiBaseUrl)).toString(),
56
+ capabilities: normalizeStringArray(request.capabilities),
57
+ candidates,
58
+ limits,
59
+ };
60
+ await this.appendSession(nodeId, 'p2p', session);
61
+ return session;
62
+ }
63
+ async getP2PSession(nodeId, sessionId) {
64
+ const { session } = await this.loadP2PSession(nodeId, sessionId);
65
+ this.assertP2PSessionActive(session);
66
+ return session;
67
+ }
68
+ async listP2PSessions(nodeId) {
69
+ const current = await this.options.repository.getNodeMetadata(nodeId);
70
+ if (!current) {
71
+ throw new NodeRouteSourceNotFoundError(`Node ${nodeId} not found`);
72
+ }
73
+ const metadata = current.metadata ?? {};
74
+ const reachabilitySessions = isRecord(metadata.reachabilitySessions) ? metadata.reachabilitySessions : {};
75
+ const sessions = Array.isArray(reachabilitySessions.p2p)
76
+ ? reachabilitySessions.p2p
77
+ .map(toP2PSession)
78
+ .filter((session) => Boolean(session))
79
+ .filter((session) => this.isP2PSessionActive(session))
80
+ : [];
81
+ return { kind: 'p2p', sessions };
82
+ }
83
+ async addP2PCandidates(nodeId, sessionId, request) {
84
+ const { reachabilitySessions, p2pSessions, sessionIndex, session } = await this.loadP2PSession(nodeId, sessionId);
85
+ this.assertP2PSessionActive(session);
86
+ const limits = this.resolveP2PSessionLimits(session);
87
+ if (request.candidates.length > limits.maxCandidatesPerUpdate) {
88
+ throw new P2PCandidateUpdateLimitExceededError('P2P candidate update limit exceeded');
89
+ }
90
+ const normalizedCandidates = this.normalizeP2PCandidates(request.candidates, {
91
+ role: request.role,
92
+ sourceId: request.sourceId,
93
+ createdAt: this.now(),
94
+ });
95
+ const nextCandidates = [
96
+ ...session.candidates,
97
+ ...normalizedCandidates,
98
+ ];
99
+ if (nextCandidates.length > limits.maxCandidatesTotal) {
100
+ throw new P2PCandidateSessionLimitExceededError('P2P candidate session limit exceeded');
101
+ }
102
+ const nextSession = {
103
+ ...session,
104
+ candidates: nextCandidates,
105
+ };
106
+ const nextP2PSessions = [...p2pSessions];
107
+ nextP2PSessions[sessionIndex] = nextSession;
108
+ await this.options.repository.mergeNodeMetadata(nodeId, {
109
+ reachabilitySessions: {
110
+ ...reachabilitySessions,
111
+ p2p: nextP2PSessions,
112
+ },
113
+ });
114
+ return nextSession;
115
+ }
116
+ async createRelaySession(nodeId, request) {
117
+ const source = await this.loadNodeRouteSource(nodeId);
118
+ const reason = request.reason.trim();
119
+ if (!reason) {
120
+ throw new InvalidRelaySessionRequestError('reason is required for relay sessions');
121
+ }
122
+ const createdAt = this.now();
123
+ const ttlSeconds = clampPositiveInteger(request.ttlSeconds, this.defaultRelayTtlSeconds, this.defaultRelayTtlSeconds);
124
+ const expiresAt = addSeconds(createdAt, ttlSeconds);
125
+ const suffix = this.randomId();
126
+ const sessionId = `relay_${suffix}`;
127
+ const auditId = `audit_${suffix}`;
128
+ const canonicalUrl = (0, RouteSetBuilder_1.buildRouteSet)(source, {
129
+ audience: 'managed',
130
+ baseStorageDomain: this.options.baseStorageDomain,
131
+ now: createdAt,
132
+ }).canonicalUrl;
133
+ const route = {
134
+ id: sessionId,
135
+ nodeId,
136
+ canonicalUrl,
137
+ kind: 'xpod-relay',
138
+ targetUrl: canonicalUrl,
139
+ priority: 90,
140
+ requiresManagedClient: false,
141
+ visibility: 'public',
142
+ health: 'unknown',
143
+ expiresAt: expiresAt.toISOString(),
144
+ metadata: { auditId, reason },
145
+ };
146
+ const session = {
147
+ sessionId,
148
+ kind: 'relay',
149
+ auditId,
150
+ nodeId,
151
+ createdAt: createdAt.toISOString(),
152
+ expiresAt: expiresAt.toISOString(),
153
+ reason,
154
+ bandwidthLimitBytes: clampPositiveInteger(request.bandwidthLimitBytes, this.defaultRelayBandwidthLimitBytes, this.defaultRelayBandwidthLimitBytes),
155
+ bandwidthLimitBps: normalizePositiveInteger(request.bandwidthLimitBps),
156
+ route,
157
+ };
158
+ await this.appendSession(nodeId, 'relay', session);
159
+ return session;
160
+ }
161
+ async loadNodeRouteSource(nodeId) {
162
+ const [metadataRow, connectivity] = await Promise.all([
163
+ this.options.repository.getNodeMetadata(nodeId),
164
+ this.options.repository.getNodeConnectivityInfo(nodeId),
165
+ ]);
166
+ if (!metadataRow && !connectivity) {
167
+ throw new NodeRouteSourceNotFoundError(`Node ${nodeId} not found`);
168
+ }
169
+ const metadata = metadataRow?.metadata ?? {};
170
+ return {
171
+ nodeId,
172
+ canonicalUrl: getString(metadata.canonicalUrl),
173
+ publicUrl: connectivity?.publicUrl,
174
+ subdomain: connectivity?.subdomain,
175
+ baseStorageDomain: this.options.baseStorageDomain,
176
+ ipv4: connectivity?.ipv4,
177
+ publicPort: connectivity?.publicPort,
178
+ connectivityStatus: connectivity?.connectivityStatus,
179
+ metadata,
180
+ };
181
+ }
182
+ readActiveP2PSessions(metadata) {
183
+ const reachabilitySessions = isRecord(metadata?.reachabilitySessions) ? metadata.reachabilitySessions : {};
184
+ return Array.isArray(reachabilitySessions.p2p)
185
+ ? reachabilitySessions.p2p
186
+ .map(toP2PSession)
187
+ .filter((session) => Boolean(session))
188
+ .filter((session) => this.isP2PSessionActive(session))
189
+ : [];
190
+ }
191
+ p2pSessionLimits() {
192
+ return {
193
+ maxCandidatesPerUpdate: this.maxP2PCandidatesPerUpdate,
194
+ maxCandidatesTotal: this.maxP2PCandidatesPerSession,
195
+ };
196
+ }
197
+ resolveP2PSessionLimits(session) {
198
+ return normalizeP2PSessionLimits(session.limits) ?? this.p2pSessionLimits();
199
+ }
200
+ async appendSession(nodeId, key, session) {
201
+ const current = await this.options.repository.getNodeMetadata(nodeId);
202
+ const metadata = current?.metadata ?? {};
203
+ const existing = isRecord(metadata.reachabilitySessions) ? metadata.reachabilitySessions : {};
204
+ const previousSessions = Array.isArray(existing[key]) ? existing[key] : [];
205
+ await this.options.repository.mergeNodeMetadata(nodeId, {
206
+ reachabilitySessions: {
207
+ ...existing,
208
+ [key]: [...previousSessions, session],
209
+ },
210
+ });
211
+ }
212
+ async loadP2PSession(nodeId, sessionId) {
213
+ const current = await this.options.repository.getNodeMetadata(nodeId);
214
+ if (!current) {
215
+ throw new NodeRouteSourceNotFoundError(`Node ${nodeId} not found`);
216
+ }
217
+ const metadata = current.metadata ?? {};
218
+ const reachabilitySessions = isRecord(metadata.reachabilitySessions) ? metadata.reachabilitySessions : {};
219
+ const p2pSessions = Array.isArray(reachabilitySessions.p2p)
220
+ ? reachabilitySessions.p2p.map(toP2PSession).filter((session) => Boolean(session))
221
+ : [];
222
+ const sessionIndex = p2pSessions.findIndex((session) => session.sessionId === sessionId);
223
+ if (sessionIndex < 0) {
224
+ throw new P2PSessionNotFoundError(`P2P session ${sessionId} not found`);
225
+ }
226
+ return {
227
+ reachabilitySessions,
228
+ p2pSessions,
229
+ sessionIndex,
230
+ session: p2pSessions[sessionIndex],
231
+ };
232
+ }
233
+ assertP2PSessionActive(session) {
234
+ if (!this.isP2PSessionActive(session)) {
235
+ throw new P2PSessionExpiredError(`P2P session ${session.sessionId} expired`);
236
+ }
237
+ }
238
+ isP2PSessionActive(session) {
239
+ const expiresAt = Date.parse(session.expiresAt);
240
+ return !Number.isFinite(expiresAt) || expiresAt > this.now().getTime();
241
+ }
242
+ normalizeP2PCandidates(value, context) {
243
+ if (!Array.isArray(value)) {
244
+ return [];
245
+ }
246
+ const createdAt = context.createdAt.toISOString();
247
+ return value
248
+ .map((entry) => {
249
+ if (!isRecord(entry)) {
250
+ return undefined;
251
+ }
252
+ const candidate = {
253
+ id: getString(entry.id) ?? `candidate_${this.randomId()}`,
254
+ role: context.role,
255
+ sourceId: context.sourceId,
256
+ createdAt: getString(entry.createdAt) ?? createdAt,
257
+ };
258
+ copyString(entry, candidate, 'protocol');
259
+ copyString(entry, candidate, 'transport');
260
+ copyString(entry, candidate, 'host');
261
+ copyString(entry, candidate, 'address');
262
+ copyString(entry, candidate, 'url');
263
+ const port = normalizePort(entry.port);
264
+ if (port !== undefined) {
265
+ candidate.port = port;
266
+ }
267
+ const priority = normalizeNumber(entry.priority);
268
+ if (priority !== undefined) {
269
+ candidate.priority = priority;
270
+ }
271
+ if (isRecord(entry.metadata)) {
272
+ candidate.metadata = entry.metadata;
273
+ }
274
+ return hasCandidateLocator(candidate) ? candidate : undefined;
275
+ })
276
+ .filter((candidate) => Boolean(candidate));
277
+ }
278
+ }
279
+ exports.ReachabilitySessionService = ReachabilitySessionService;
280
+ class InvalidRelaySessionRequestError extends Error {
281
+ }
282
+ exports.InvalidRelaySessionRequestError = InvalidRelaySessionRequestError;
283
+ class NodeRouteSourceNotFoundError extends Error {
284
+ }
285
+ exports.NodeRouteSourceNotFoundError = NodeRouteSourceNotFoundError;
286
+ class P2PActiveSessionLimitExceededError extends Error {
287
+ }
288
+ exports.P2PActiveSessionLimitExceededError = P2PActiveSessionLimitExceededError;
289
+ class P2PCandidateUpdateLimitExceededError extends Error {
290
+ }
291
+ exports.P2PCandidateUpdateLimitExceededError = P2PCandidateUpdateLimitExceededError;
292
+ class P2PCandidateSessionLimitExceededError extends Error {
293
+ }
294
+ exports.P2PCandidateSessionLimitExceededError = P2PCandidateSessionLimitExceededError;
295
+ class P2PSessionExpiredError extends Error {
296
+ }
297
+ exports.P2PSessionExpiredError = P2PSessionExpiredError;
298
+ class P2PSessionNotFoundError extends Error {
299
+ }
300
+ exports.P2PSessionNotFoundError = P2PSessionNotFoundError;
301
+ function addSeconds(date, seconds) {
302
+ return new Date(date.getTime() + seconds * 1000);
303
+ }
304
+ function clampPositiveInteger(value, fallback, maximum) {
305
+ const normalized = normalizePositiveInteger(value);
306
+ if (!normalized) {
307
+ return fallback;
308
+ }
309
+ return Math.min(normalized, maximum);
310
+ }
311
+ function normalizePositiveInteger(value) {
312
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
313
+ return undefined;
314
+ }
315
+ return Math.floor(value);
316
+ }
317
+ function normalizeP2PSessionLimits(value) {
318
+ if (!isRecord(value)) {
319
+ return undefined;
320
+ }
321
+ const maxCandidatesPerUpdate = normalizePositiveInteger(value.maxCandidatesPerUpdate);
322
+ const maxCandidatesTotal = normalizePositiveInteger(value.maxCandidatesTotal);
323
+ if (!maxCandidatesPerUpdate || !maxCandidatesTotal) {
324
+ return undefined;
325
+ }
326
+ return { maxCandidatesPerUpdate, maxCandidatesTotal };
327
+ }
328
+ function normalizePort(value) {
329
+ const port = normalizePositiveInteger(value);
330
+ if (!port || port > 65535) {
331
+ return undefined;
332
+ }
333
+ return port;
334
+ }
335
+ function normalizeNumber(value) {
336
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
337
+ return undefined;
338
+ }
339
+ return value;
340
+ }
341
+ function normalizeStringArray(value) {
342
+ if (!Array.isArray(value)) {
343
+ return [];
344
+ }
345
+ return value.filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
346
+ }
347
+ function getString(value) {
348
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
349
+ }
350
+ function isRecord(value) {
351
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
352
+ }
353
+ function copyString(source, target, key) {
354
+ const value = getString(source[key]);
355
+ if (value) {
356
+ target[key] = value;
357
+ }
358
+ }
359
+ function hasCandidateLocator(candidate) {
360
+ return Boolean(candidate.host || candidate.address || candidate.url || candidate.port);
361
+ }
362
+ function toP2PSession(value) {
363
+ if (!isRecord(value) || value.kind !== 'p2p') {
364
+ return undefined;
365
+ }
366
+ const sessionId = getString(value.sessionId);
367
+ const nodeId = getString(value.nodeId);
368
+ const clientId = getString(value.clientId);
369
+ const auditId = getString(value.auditId);
370
+ const owner = normalizeP2PSessionOwner(value.owner);
371
+ const createdAt = getString(value.createdAt);
372
+ const expiresAt = getString(value.expiresAt);
373
+ const signalingUrl = getString(value.signalingUrl);
374
+ const limits = normalizeP2PSessionLimits(value.limits);
375
+ if (!sessionId || !nodeId || !clientId || !createdAt || !expiresAt || !signalingUrl) {
376
+ return undefined;
377
+ }
378
+ return {
379
+ sessionId,
380
+ kind: 'p2p',
381
+ nodeId,
382
+ clientId,
383
+ ...(owner ? { owner } : {}),
384
+ ...(auditId ? { auditId } : {}),
385
+ createdAt,
386
+ expiresAt,
387
+ nodeCandidates: Array.isArray(value.nodeCandidates) ? value.nodeCandidates : [],
388
+ signalingUrl,
389
+ capabilities: normalizeStringArray(value.capabilities),
390
+ candidates: Array.isArray(value.candidates)
391
+ ? value.candidates.map(toP2PTransportCandidate).filter((candidate) => Boolean(candidate))
392
+ : [],
393
+ ...(limits ? { limits } : {}),
394
+ };
395
+ }
396
+ function normalizeP2PSessionOwner(value) {
397
+ if (!isRecord(value) || value.type !== 'solid') {
398
+ return undefined;
399
+ }
400
+ const webId = getString(value.webId);
401
+ return webId ? { type: 'solid', webId } : undefined;
402
+ }
403
+ function toP2PTransportCandidate(value) {
404
+ if (!isRecord(value)) {
405
+ return undefined;
406
+ }
407
+ const id = getString(value.id);
408
+ const sourceId = getString(value.sourceId);
409
+ const createdAt = getString(value.createdAt);
410
+ const role = value.role === 'node' || value.role === 'client' ? value.role : undefined;
411
+ if (!id || !role || !sourceId || !createdAt) {
412
+ return undefined;
413
+ }
414
+ const candidate = { id, role, sourceId, createdAt };
415
+ copyString(value, candidate, 'protocol');
416
+ copyString(value, candidate, 'transport');
417
+ copyString(value, candidate, 'host');
418
+ copyString(value, candidate, 'address');
419
+ copyString(value, candidate, 'url');
420
+ const port = normalizePort(value.port);
421
+ if (port !== undefined) {
422
+ candidate.port = port;
423
+ }
424
+ const priority = normalizeNumber(value.priority);
425
+ if (priority !== undefined) {
426
+ candidate.priority = priority;
427
+ }
428
+ if (isRecord(value.metadata)) {
429
+ candidate.metadata = value.metadata;
430
+ }
431
+ return candidate;
432
+ }
433
+ function randomString() {
434
+ return Math.random().toString(36).slice(2, 12);
435
+ }
436
+ function resolveApiBaseUrl(value) {
437
+ return typeof value === 'function' ? value() : value;
438
+ }
439
+ //# sourceMappingURL=ReachabilitySessionService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReachabilitySessionService.js","sourceRoot":"","sources":["../../../src/edge/reachability/ReachabilitySessionService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { buildRouteSet } from './RouteSetBuilder';\nimport type {\n AccessRoute,\n BuildRouteSetSource,\n P2PCandidateUpdateRequest,\n P2PSession,\n P2PSessionLimits,\n P2PSessionList,\n P2PSessionOwner,\n P2PSessionRequest,\n P2PTransportCandidate,\n RelaySession,\n RelaySessionRequest,\n} from './types';\n\nexport interface ReachabilitySessionServiceOptions {\n repository: EdgeNodeRepository;\n baseStorageDomain?: string;\n apiBaseUrl: string | (() => string);\n now?: () => Date;\n randomId?: () => string;\n defaultP2PTtlSeconds?: number;\n defaultRelayTtlSeconds?: number;\n defaultRelayBandwidthLimitBytes?: number;\n maxActiveP2PSessionsPerNode?: number;\n maxP2PCandidatesPerUpdate?: number;\n maxP2PCandidatesPerSession?: number;\n}\n\nexport class ReachabilitySessionService {\n private readonly now: () => Date;\n private readonly randomId: () => string;\n private readonly defaultP2PTtlSeconds: number;\n private readonly defaultRelayTtlSeconds: number;\n private readonly defaultRelayBandwidthLimitBytes: number;\n private readonly maxActiveP2PSessionsPerNode: number;\n private readonly maxP2PCandidatesPerUpdate: number;\n private readonly maxP2PCandidatesPerSession: number;\n\n public constructor(private readonly options: ReachabilitySessionServiceOptions) {\n this.now = options.now ?? (() => new Date());\n this.randomId = options.randomId ?? randomString;\n this.defaultP2PTtlSeconds = options.defaultP2PTtlSeconds ?? 5 * 60;\n this.defaultRelayTtlSeconds = options.defaultRelayTtlSeconds ?? 15 * 60;\n this.defaultRelayBandwidthLimitBytes = options.defaultRelayBandwidthLimitBytes ?? 64 * 1024 * 1024;\n this.maxActiveP2PSessionsPerNode = options.maxActiveP2PSessionsPerNode ?? 16;\n this.maxP2PCandidatesPerUpdate = options.maxP2PCandidatesPerUpdate ?? 32;\n this.maxP2PCandidatesPerSession = options.maxP2PCandidatesPerSession ?? 256;\n }\n\n public async createP2PSession(nodeId: string, request: P2PSessionRequest): Promise<P2PSession> {\n const source = await this.loadNodeRouteSource(nodeId);\n const activeSessions = this.readActiveP2PSessions(source.metadata);\n if (activeSessions.length >= this.maxActiveP2PSessionsPerNode) {\n throw new P2PActiveSessionLimitExceededError(`Node ${nodeId} reached active P2P session limit`);\n }\n const createdAt = this.now();\n const expiresAt = addSeconds(createdAt, this.defaultP2PTtlSeconds);\n const suffix = this.randomId();\n const sessionId = `p2p_${suffix}`;\n const auditId = `audit_${suffix}`;\n const limits = this.p2pSessionLimits();\n if (Array.isArray(request.candidates) && request.candidates.length > limits.maxCandidatesPerUpdate) {\n throw new P2PCandidateUpdateLimitExceededError('P2P candidate update limit exceeded');\n }\n const candidates = this.normalizeP2PCandidates(request.candidates, {\n role: 'client',\n sourceId: request.clientId,\n createdAt,\n });\n if (candidates.length > limits.maxCandidatesTotal) {\n throw new P2PCandidateSessionLimitExceededError('P2P candidate session limit exceeded');\n }\n const routeSet = buildRouteSet(source, {\n audience: 'managed',\n baseStorageDomain: this.options.baseStorageDomain,\n now: createdAt,\n });\n const session: P2PSession = {\n sessionId,\n kind: 'p2p',\n nodeId,\n clientId: request.clientId,\n ...(request.owner ? { owner: request.owner } : {}),\n auditId,\n createdAt: createdAt.toISOString(),\n expiresAt: expiresAt.toISOString(),\n nodeCandidates: routeSet.routes,\n signalingUrl: new URL(\n `/v1/signal/nodes/${encodeURIComponent(nodeId)}/sessions/${sessionId}`,\n resolveApiBaseUrl(this.options.apiBaseUrl),\n ).toString(),\n capabilities: normalizeStringArray(request.capabilities),\n candidates,\n limits,\n };\n await this.appendSession(nodeId, 'p2p', session);\n return session;\n }\n\n public async getP2PSession(nodeId: string, sessionId: string): Promise<P2PSession> {\n const { session } = await this.loadP2PSession(nodeId, sessionId);\n this.assertP2PSessionActive(session);\n return session;\n }\n\n public async listP2PSessions(nodeId: string): Promise<P2PSessionList> {\n const current = await this.options.repository.getNodeMetadata(nodeId);\n if (!current) {\n throw new NodeRouteSourceNotFoundError(`Node ${nodeId} not found`);\n }\n const metadata = current.metadata ?? {};\n const reachabilitySessions = isRecord(metadata.reachabilitySessions) ? metadata.reachabilitySessions : {};\n const sessions = Array.isArray(reachabilitySessions.p2p)\n ? reachabilitySessions.p2p\n .map(toP2PSession)\n .filter((session): session is P2PSession => Boolean(session))\n .filter((session) => this.isP2PSessionActive(session))\n : [];\n return { kind: 'p2p', sessions };\n }\n\n public async addP2PCandidates(\n nodeId: string,\n sessionId: string,\n request: P2PCandidateUpdateRequest,\n ): Promise<P2PSession> {\n const { reachabilitySessions, p2pSessions, sessionIndex, session } = await this.loadP2PSession(nodeId, sessionId);\n this.assertP2PSessionActive(session);\n\n const limits = this.resolveP2PSessionLimits(session);\n if (request.candidates.length > limits.maxCandidatesPerUpdate) {\n throw new P2PCandidateUpdateLimitExceededError('P2P candidate update limit exceeded');\n }\n const normalizedCandidates = this.normalizeP2PCandidates(request.candidates, {\n role: request.role,\n sourceId: request.sourceId,\n createdAt: this.now(),\n });\n const nextCandidates = [\n ...session.candidates,\n ...normalizedCandidates,\n ];\n if (nextCandidates.length > limits.maxCandidatesTotal) {\n throw new P2PCandidateSessionLimitExceededError('P2P candidate session limit exceeded');\n }\n const nextSession: P2PSession = {\n ...session,\n candidates: nextCandidates,\n };\n const nextP2PSessions = [...p2pSessions];\n nextP2PSessions[sessionIndex] = nextSession;\n\n await this.options.repository.mergeNodeMetadata(nodeId, {\n reachabilitySessions: {\n ...reachabilitySessions,\n p2p: nextP2PSessions,\n },\n });\n return nextSession;\n }\n\n public async createRelaySession(nodeId: string, request: RelaySessionRequest): Promise<RelaySession> {\n const source = await this.loadNodeRouteSource(nodeId);\n const reason = request.reason.trim();\n if (!reason) {\n throw new InvalidRelaySessionRequestError('reason is required for relay sessions');\n }\n const createdAt = this.now();\n const ttlSeconds = clampPositiveInteger(request.ttlSeconds, this.defaultRelayTtlSeconds, this.defaultRelayTtlSeconds);\n const expiresAt = addSeconds(createdAt, ttlSeconds);\n const suffix = this.randomId();\n const sessionId = `relay_${suffix}`;\n const auditId = `audit_${suffix}`;\n const canonicalUrl = buildRouteSet(source, {\n audience: 'managed',\n baseStorageDomain: this.options.baseStorageDomain,\n now: createdAt,\n }).canonicalUrl;\n const route: AccessRoute = {\n id: sessionId,\n nodeId,\n canonicalUrl,\n kind: 'xpod-relay',\n targetUrl: canonicalUrl,\n priority: 90,\n requiresManagedClient: false,\n visibility: 'public',\n health: 'unknown',\n expiresAt: expiresAt.toISOString(),\n metadata: { auditId, reason },\n };\n const session: RelaySession = {\n sessionId,\n kind: 'relay',\n auditId,\n nodeId,\n createdAt: createdAt.toISOString(),\n expiresAt: expiresAt.toISOString(),\n reason,\n bandwidthLimitBytes: clampPositiveInteger(\n request.bandwidthLimitBytes,\n this.defaultRelayBandwidthLimitBytes,\n this.defaultRelayBandwidthLimitBytes,\n ),\n bandwidthLimitBps: normalizePositiveInteger(request.bandwidthLimitBps),\n route,\n };\n await this.appendSession(nodeId, 'relay', session);\n return session;\n }\n\n private async loadNodeRouteSource(nodeId: string): Promise<BuildRouteSetSource> {\n const [metadataRow, connectivity] = await Promise.all([\n this.options.repository.getNodeMetadata(nodeId),\n this.options.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: this.options.baseStorageDomain,\n ipv4: connectivity?.ipv4,\n publicPort: connectivity?.publicPort,\n connectivityStatus: connectivity?.connectivityStatus,\n metadata,\n };\n }\n\n private readActiveP2PSessions(metadata: Record<string, unknown> | null | undefined): P2PSession[] {\n const reachabilitySessions = isRecord(metadata?.reachabilitySessions) ? metadata.reachabilitySessions : {};\n return Array.isArray(reachabilitySessions.p2p)\n ? reachabilitySessions.p2p\n .map(toP2PSession)\n .filter((session): session is P2PSession => Boolean(session))\n .filter((session) => this.isP2PSessionActive(session))\n : [];\n }\n\n private p2pSessionLimits(): P2PSessionLimits {\n return {\n maxCandidatesPerUpdate: this.maxP2PCandidatesPerUpdate,\n maxCandidatesTotal: this.maxP2PCandidatesPerSession,\n };\n }\n\n private resolveP2PSessionLimits(session: P2PSession): P2PSessionLimits {\n return normalizeP2PSessionLimits(session.limits) ?? this.p2pSessionLimits();\n }\n\n private async appendSession(nodeId: string, key: 'p2p' | 'relay', session: P2PSession | RelaySession): Promise<void> {\n const current = await this.options.repository.getNodeMetadata(nodeId);\n const metadata = current?.metadata ?? {};\n const existing = isRecord(metadata.reachabilitySessions) ? metadata.reachabilitySessions : {};\n const previousSessions = Array.isArray(existing[key]) ? existing[key] : [];\n await this.options.repository.mergeNodeMetadata(nodeId, {\n reachabilitySessions: {\n ...existing,\n [key]: [...previousSessions, session],\n },\n });\n }\n\n private async loadP2PSession(nodeId: string, sessionId: string): Promise<{\n reachabilitySessions: Record<string, unknown>;\n p2pSessions: P2PSession[];\n sessionIndex: number;\n session: P2PSession;\n }> {\n const current = await this.options.repository.getNodeMetadata(nodeId);\n if (!current) {\n throw new NodeRouteSourceNotFoundError(`Node ${nodeId} not found`);\n }\n const metadata = current.metadata ?? {};\n const reachabilitySessions = isRecord(metadata.reachabilitySessions) ? metadata.reachabilitySessions : {};\n const p2pSessions = Array.isArray(reachabilitySessions.p2p)\n ? reachabilitySessions.p2p.map(toP2PSession).filter((session): session is P2PSession => Boolean(session))\n : [];\n const sessionIndex = p2pSessions.findIndex((session) => session.sessionId === sessionId);\n if (sessionIndex < 0) {\n throw new P2PSessionNotFoundError(`P2P session ${sessionId} not found`);\n }\n return {\n reachabilitySessions,\n p2pSessions,\n sessionIndex,\n session: p2pSessions[sessionIndex],\n };\n }\n\n private assertP2PSessionActive(session: P2PSession): void {\n if (!this.isP2PSessionActive(session)) {\n throw new P2PSessionExpiredError(`P2P session ${session.sessionId} expired`);\n }\n }\n\n private isP2PSessionActive(session: P2PSession): boolean {\n const expiresAt = Date.parse(session.expiresAt);\n return !Number.isFinite(expiresAt) || expiresAt > this.now().getTime();\n }\n\n private normalizeP2PCandidates(\n value: unknown,\n context: {\n role: P2PCandidateUpdateRequest['role'];\n sourceId: string;\n createdAt: Date;\n },\n ): P2PTransportCandidate[] {\n if (!Array.isArray(value)) {\n return [];\n }\n const createdAt = context.createdAt.toISOString();\n return value\n .map((entry): P2PTransportCandidate | undefined => {\n if (!isRecord(entry)) {\n return undefined;\n }\n const candidate: P2PTransportCandidate = {\n id: getString(entry.id) ?? `candidate_${this.randomId()}`,\n role: context.role,\n sourceId: context.sourceId,\n createdAt: getString(entry.createdAt) ?? createdAt,\n };\n copyString(entry, candidate, 'protocol');\n copyString(entry, candidate, 'transport');\n copyString(entry, candidate, 'host');\n copyString(entry, candidate, 'address');\n copyString(entry, candidate, 'url');\n const port = normalizePort(entry.port);\n if (port !== undefined) {\n candidate.port = port;\n }\n const priority = normalizeNumber(entry.priority);\n if (priority !== undefined) {\n candidate.priority = priority;\n }\n if (isRecord(entry.metadata)) {\n candidate.metadata = entry.metadata;\n }\n return hasCandidateLocator(candidate) ? candidate : undefined;\n })\n .filter((candidate): candidate is P2PTransportCandidate => Boolean(candidate));\n }\n}\n\nexport class InvalidRelaySessionRequestError extends Error {}\nexport class NodeRouteSourceNotFoundError extends Error {}\nexport class P2PActiveSessionLimitExceededError extends Error {}\nexport class P2PCandidateUpdateLimitExceededError extends Error {}\nexport class P2PCandidateSessionLimitExceededError extends Error {}\nexport class P2PSessionExpiredError extends Error {}\nexport class P2PSessionNotFoundError extends Error {}\n\nfunction addSeconds(date: Date, seconds: number): Date {\n return new Date(date.getTime() + seconds * 1000);\n}\n\nfunction clampPositiveInteger(value: unknown, fallback: number, maximum: number): number {\n const normalized = normalizePositiveInteger(value);\n if (!normalized) {\n return fallback;\n }\n return Math.min(normalized, maximum);\n}\n\nfunction normalizePositiveInteger(value: unknown): number | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n return undefined;\n }\n return Math.floor(value);\n}\n\nfunction normalizeP2PSessionLimits(value: unknown): P2PSessionLimits | undefined {\n if (!isRecord(value)) {\n return undefined;\n }\n const maxCandidatesPerUpdate = normalizePositiveInteger(value.maxCandidatesPerUpdate);\n const maxCandidatesTotal = normalizePositiveInteger(value.maxCandidatesTotal);\n if (!maxCandidatesPerUpdate || !maxCandidatesTotal) {\n return undefined;\n }\n return { maxCandidatesPerUpdate, maxCandidatesTotal };\n}\n\nfunction normalizePort(value: unknown): number | undefined {\n const port = normalizePositiveInteger(value);\n if (!port || port > 65535) {\n return undefined;\n }\n return port;\n}\n\nfunction normalizeNumber(value: unknown): number | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return undefined;\n }\n return value;\n}\n\nfunction normalizeStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n return value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0);\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction copyString(\n source: Record<string, unknown>,\n target: Partial<P2PTransportCandidate>,\n key: 'protocol' | 'transport' | 'host' | 'address' | 'url',\n): void {\n const value = getString(source[key]);\n if (value) {\n target[key] = value;\n }\n}\n\nfunction hasCandidateLocator(candidate: P2PTransportCandidate): boolean {\n return Boolean(candidate.host || candidate.address || candidate.url || candidate.port);\n}\n\nfunction toP2PSession(value: unknown): P2PSession | undefined {\n if (!isRecord(value) || value.kind !== 'p2p') {\n return undefined;\n }\n const sessionId = getString(value.sessionId);\n const nodeId = getString(value.nodeId);\n const clientId = getString(value.clientId);\n const auditId = getString(value.auditId);\n const owner = normalizeP2PSessionOwner(value.owner);\n const createdAt = getString(value.createdAt);\n const expiresAt = getString(value.expiresAt);\n const signalingUrl = getString(value.signalingUrl);\n const limits = normalizeP2PSessionLimits(value.limits);\n if (!sessionId || !nodeId || !clientId || !createdAt || !expiresAt || !signalingUrl) {\n return undefined;\n }\n return {\n sessionId,\n kind: 'p2p',\n nodeId,\n clientId,\n ...(owner ? { owner } : {}),\n ...(auditId ? { auditId } : {}),\n createdAt,\n expiresAt,\n nodeCandidates: Array.isArray(value.nodeCandidates) ? value.nodeCandidates as AccessRoute[] : [],\n signalingUrl,\n capabilities: normalizeStringArray(value.capabilities),\n candidates: Array.isArray(value.candidates)\n ? value.candidates.map(toP2PTransportCandidate).filter((candidate): candidate is P2PTransportCandidate => Boolean(candidate))\n : [],\n ...(limits ? { limits } : {}),\n };\n}\n\nfunction normalizeP2PSessionOwner(value: unknown): P2PSessionOwner | undefined {\n if (!isRecord(value) || value.type !== 'solid') {\n return undefined;\n }\n const webId = getString(value.webId);\n return webId ? { type: 'solid', webId } : undefined;\n}\n\nfunction toP2PTransportCandidate(value: unknown): P2PTransportCandidate | undefined {\n if (!isRecord(value)) {\n return undefined;\n }\n const id = getString(value.id);\n const sourceId = getString(value.sourceId);\n const createdAt = getString(value.createdAt);\n const role = value.role === 'node' || value.role === 'client' ? value.role : undefined;\n if (!id || !role || !sourceId || !createdAt) {\n return undefined;\n }\n const candidate: P2PTransportCandidate = { id, role, sourceId, createdAt };\n copyString(value, candidate, 'protocol');\n copyString(value, candidate, 'transport');\n copyString(value, candidate, 'host');\n copyString(value, candidate, 'address');\n copyString(value, candidate, 'url');\n const port = normalizePort(value.port);\n if (port !== undefined) {\n candidate.port = port;\n }\n const priority = normalizeNumber(value.priority);\n if (priority !== undefined) {\n candidate.priority = priority;\n }\n if (isRecord(value.metadata)) {\n candidate.metadata = value.metadata;\n }\n return candidate;\n}\n\nfunction randomString(): string {\n return Math.random().toString(36).slice(2, 12);\n}\n\nfunction resolveApiBaseUrl(value: string | (() => string)): string {\n return typeof value === 'function' ? value() : value;\n}\n"]}