@undefineds.co/xpod 0.3.31 → 0.3.33

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 (218) hide show
  1. package/dist/api/auth/AuthContext.d.ts +3 -2
  2. package/dist/api/auth/AuthContext.js +2 -1
  3. package/dist/api/auth/AuthContext.js.map +1 -1
  4. package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +2 -12
  5. package/dist/api/auth/ClientCredentialsAuthenticator.js +4 -4
  6. package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
  7. package/dist/api/auth/ServiceTokenAuthenticator.d.ts +2 -2
  8. package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -1
  9. package/dist/api/chatkit/pod-store.d.ts +3 -0
  10. package/dist/api/chatkit/pod-store.js +93 -59
  11. package/dist/api/chatkit/pod-store.js.map +1 -1
  12. package/dist/api/chatkit/schema.d.ts +5 -6
  13. package/dist/api/container/business-token.d.ts +1 -1
  14. package/dist/api/container/business-token.js +5 -1
  15. package/dist/api/container/business-token.js.map +1 -1
  16. package/dist/api/container/common.js +14 -10
  17. package/dist/api/container/common.js.map +1 -1
  18. package/dist/api/container/routes.js +16 -3
  19. package/dist/api/container/routes.js.map +1 -1
  20. package/dist/api/container/types.d.ts +2 -4
  21. package/dist/api/container/types.js.map +1 -1
  22. package/dist/api/handlers/ChatHandler.d.ts +1 -1
  23. package/dist/api/handlers/ChatHandler.js +1 -1
  24. package/dist/api/handlers/ChatHandler.js.map +1 -1
  25. package/dist/api/handlers/EdgeNodeSignalHandler.js +3 -1
  26. package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -1
  27. package/dist/api/handlers/PodManagementHandler.d.ts +2 -0
  28. package/dist/api/handlers/PodManagementHandler.js +114 -12
  29. package/dist/api/handlers/PodManagementHandler.js.map +1 -1
  30. package/dist/api/handlers/ProvisionHandler.d.ts +27 -0
  31. package/dist/api/handlers/ProvisionHandler.js +339 -32
  32. package/dist/api/handlers/ProvisionHandler.js.map +1 -1
  33. package/dist/api/handlers/QuotaHandler.js +0 -12
  34. package/dist/api/handlers/QuotaHandler.js.map +1 -1
  35. package/dist/api/handlers/index.d.ts +0 -1
  36. package/dist/api/handlers/index.js +0 -1
  37. package/dist/api/handlers/index.js.map +1 -1
  38. package/dist/api/matrix/PodMatrixStore.js +26 -21
  39. package/dist/api/matrix/PodMatrixStore.js.map +1 -1
  40. package/dist/api/runs/InngestRunExecutionBackend.d.ts +2 -2
  41. package/dist/api/runs/schema.d.ts +5 -7
  42. package/dist/api/runs/store.js +6 -4
  43. package/dist/api/runs/store.js.map +1 -1
  44. package/dist/api/runtime.js +3 -3
  45. package/dist/api/runtime.js.map +1 -1
  46. package/dist/api/tasks/InngestTaskScheduler.d.ts +4 -4
  47. package/dist/api/tasks/schema.d.ts +17 -13
  48. package/dist/api/tasks/schema.js +7 -2
  49. package/dist/api/tasks/schema.js.map +1 -1
  50. package/dist/api/tasks/store.js +1 -2
  51. package/dist/api/tasks/store.js.map +1 -1
  52. package/dist/components/context.jsonld +12 -0
  53. package/dist/edge/EdgeNodeAgent.d.ts +1 -1
  54. package/dist/edge/EdgeNodeAgent.js +1 -1
  55. package/dist/edge/EdgeNodeAgent.js.map +1 -1
  56. package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -0
  57. package/dist/edge/EdgeNodeDnsCoordinator.js +9 -3
  58. package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
  59. package/dist/edge/EdgeNodeDnsCoordinator.jsonld +4 -0
  60. package/dist/edge/EdgeNodeHealthProbeService.d.ts +3 -0
  61. package/dist/edge/EdgeNodeHealthProbeService.js +22 -2
  62. package/dist/edge/EdgeNodeHealthProbeService.js.map +1 -1
  63. package/dist/edge/EdgeNodeHealthProbeService.jsonld +12 -0
  64. package/dist/http/ClusterIngressRouter.js +6 -3
  65. package/dist/http/ClusterIngressRouter.js.map +1 -1
  66. package/dist/http/ClusterWebSocketConfigurator.js +6 -2
  67. package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
  68. package/dist/http/EdgeNodeDirectDebugHttpHandler.d.ts +2 -0
  69. package/dist/http/EdgeNodeDirectDebugHttpHandler.js +18 -3
  70. package/dist/http/EdgeNodeDirectDebugHttpHandler.js.map +1 -1
  71. package/dist/http/EdgeNodeDirectDebugHttpHandler.jsonld +8 -0
  72. package/dist/http/EdgeNodeProxyHttpHandler.js +6 -2
  73. package/dist/http/EdgeNodeProxyHttpHandler.js.map +1 -1
  74. package/dist/http/cluster/PodMigrationHttpHandler.d.ts +2 -2
  75. package/dist/http/cluster/PodMigrationHttpHandler.js +2 -2
  76. package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
  77. package/dist/http/quota/QuotaAdminHttpHandler.js +27 -21
  78. package/dist/http/quota/QuotaAdminHttpHandler.js.map +1 -1
  79. package/dist/http/search/SearchHttpHandler.js +2 -2
  80. package/dist/http/search/SearchHttpHandler.js.map +1 -1
  81. package/dist/identity/drizzle/AccountRepository.d.ts +4 -22
  82. package/dist/identity/drizzle/AccountRepository.js +9 -113
  83. package/dist/identity/drizzle/AccountRepository.js.map +1 -1
  84. package/dist/identity/drizzle/AccountRoleRepository.d.ts +5 -5
  85. package/dist/identity/drizzle/AccountRoleRepository.js +204 -97
  86. package/dist/identity/drizzle/AccountRoleRepository.js.map +1 -1
  87. package/dist/identity/drizzle/DdnsRepository.d.ts +5 -20
  88. package/dist/identity/drizzle/DdnsRepository.js +13 -49
  89. package/dist/identity/drizzle/DdnsRepository.js.map +1 -1
  90. package/dist/identity/drizzle/EdgeNodeRepository.d.ts +13 -6
  91. package/dist/identity/drizzle/EdgeNodeRepository.js +167 -66
  92. package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
  93. package/dist/identity/drizzle/PodLookupRepository.d.ts +7 -36
  94. package/dist/identity/drizzle/PodLookupRepository.js +103 -126
  95. package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
  96. package/dist/identity/drizzle/ServiceTokenRepository.d.ts +13 -1
  97. package/dist/identity/drizzle/ServiceTokenRepository.js +7 -0
  98. package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -1
  99. package/dist/identity/drizzle/db.d.ts +2 -1
  100. package/dist/identity/drizzle/db.js +173 -297
  101. package/dist/identity/drizzle/db.js.map +1 -1
  102. package/dist/identity/drizzle/schema.pg.d.ts +3 -11
  103. package/dist/identity/drizzle/schema.pg.js +10 -45
  104. package/dist/identity/drizzle/schema.pg.js.map +1 -1
  105. package/dist/identity/drizzle/schema.sqlite.d.ts +88 -531
  106. package/dist/identity/drizzle/schema.sqlite.js +13 -46
  107. package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
  108. package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +3 -0
  109. package/dist/identity/oidc/ScopedPickWebIdHandler.js +18 -6
  110. package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -1
  111. package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +22 -0
  112. package/dist/provision/ProvisionCodeCodec.js +10 -1
  113. package/dist/provision/ProvisionCodeCodec.js.map +1 -1
  114. package/dist/provision/ProvisionPodCreator.d.ts +8 -2
  115. package/dist/provision/ProvisionPodCreator.js +134 -41
  116. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  117. package/dist/provision/ProvisionPodCreator.jsonld +38 -3
  118. package/dist/quota/DrizzleQuotaService.d.ts +0 -4
  119. package/dist/quota/DrizzleQuotaService.js +1 -21
  120. package/dist/quota/DrizzleQuotaService.js.map +1 -1
  121. package/dist/quota/DrizzleQuotaService.jsonld +0 -16
  122. package/dist/quota/NoopQuotaService.d.ts +0 -4
  123. package/dist/quota/NoopQuotaService.js +0 -8
  124. package/dist/quota/NoopQuotaService.js.map +1 -1
  125. package/dist/quota/NoopQuotaService.jsonld +0 -16
  126. package/dist/quota/QuotaService.d.ts +0 -4
  127. package/dist/quota/QuotaService.js.map +1 -1
  128. package/dist/quota/QuotaService.jsonld +0 -16
  129. package/dist/service/EdgeNodeSignalClient.d.ts +0 -2
  130. package/dist/service/EdgeNodeSignalClient.js +0 -4
  131. package/dist/service/EdgeNodeSignalClient.js.map +1 -1
  132. package/dist/service/PodMigrationService.d.ts +2 -2
  133. package/dist/service/PodMigrationService.js +4 -4
  134. package/dist/service/PodMigrationService.js.map +1 -1
  135. package/dist/setup/LocalSetupServiceTokenRepository.d.ts +22 -0
  136. package/dist/setup/LocalSetupServiceTokenRepository.js +68 -0
  137. package/dist/setup/LocalSetupServiceTokenRepository.js.map +1 -0
  138. package/dist/storage/quota/PerAccountQuotaStrategy.js +2 -2
  139. package/dist/storage/quota/PerAccountQuotaStrategy.js.map +1 -1
  140. package/dist/storage/quota/UsageRepository.d.ts +10 -32
  141. package/dist/storage/quota/UsageRepository.js +84 -281
  142. package/dist/storage/quota/UsageRepository.js.map +1 -1
  143. package/dist/storage/vector/VectorIndexingListener.js +2 -2
  144. package/dist/storage/vector/VectorIndexingListener.js.map +1 -1
  145. package/dist/subdomain/SubdomainService.d.ts +1 -1
  146. package/dist/subdomain/SubdomainService.js +1 -1
  147. package/dist/subdomain/SubdomainService.js.map +1 -1
  148. package/dist/subdomain/SubdomainService.jsonld +1 -1
  149. package/node_modules/@undefineds.co/drizzle-solid/dist/core/exact-records.d.ts +21 -0
  150. package/node_modules/@undefineds.co/drizzle-solid/dist/core/exact-records.d.ts.map +1 -0
  151. package/node_modules/@undefineds.co/drizzle-solid/dist/core/exact-records.js +85 -0
  152. package/node_modules/@undefineds.co/drizzle-solid/dist/core/exact-records.js.map +1 -0
  153. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/default-id-template.d.ts +10 -0
  154. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/default-id-template.d.ts.map +1 -0
  155. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/default-id-template.js +365 -0
  156. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/default-id-template.js.map +1 -0
  157. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/index.d.ts +1 -0
  158. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/index.d.ts.map +1 -1
  159. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/index.js +3 -1
  160. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/index.js.map +1 -1
  161. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/insert-query-builder.d.ts.map +1 -1
  162. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/insert-query-builder.js +5 -1
  163. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/insert-query-builder.js.map +1 -1
  164. package/node_modules/@undefineds.co/drizzle-solid/dist/core/repository.d.ts +2 -0
  165. package/node_modules/@undefineds.co/drizzle-solid/dist/core/repository.d.ts.map +1 -1
  166. package/node_modules/@undefineds.co/drizzle-solid/dist/core/repository.js +2 -1
  167. package/node_modules/@undefineds.co/drizzle-solid/dist/core/repository.js.map +1 -1
  168. package/node_modules/@undefineds.co/drizzle-solid/dist/core/resource-reference.d.ts +18 -0
  169. package/node_modules/@undefineds.co/drizzle-solid/dist/core/resource-reference.d.ts.map +1 -1
  170. package/node_modules/@undefineds.co/drizzle-solid/dist/core/resource-reference.js +234 -10
  171. package/node_modules/@undefineds.co/drizzle-solid/dist/core/resource-reference.js.map +1 -1
  172. package/node_modules/@undefineds.co/drizzle-solid/dist/core/schema/pod-table.d.ts +13 -0
  173. package/node_modules/@undefineds.co/drizzle-solid/dist/core/schema/pod-table.d.ts.map +1 -1
  174. package/node_modules/@undefineds.co/drizzle-solid/dist/core/schema/pod-table.js +19 -0
  175. package/node_modules/@undefineds.co/drizzle-solid/dist/core/schema/pod-table.js.map +1 -1
  176. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/exact-records.d.ts +21 -0
  177. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/exact-records.d.ts.map +1 -0
  178. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/exact-records.js +78 -0
  179. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/exact-records.js.map +1 -0
  180. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/default-id-template.d.ts +10 -0
  181. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/default-id-template.d.ts.map +1 -0
  182. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/default-id-template.js +362 -0
  183. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/default-id-template.js.map +1 -0
  184. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/index.d.ts +1 -0
  185. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/index.d.ts.map +1 -1
  186. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/index.js +1 -0
  187. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/index.js.map +1 -1
  188. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/insert-query-builder.d.ts.map +1 -1
  189. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/insert-query-builder.js +5 -1
  190. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/insert-query-builder.js.map +1 -1
  191. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/repository.d.ts +2 -0
  192. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/repository.d.ts.map +1 -1
  193. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/repository.js +1 -0
  194. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/repository.js.map +1 -1
  195. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/resource-reference.d.ts +18 -0
  196. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/resource-reference.d.ts.map +1 -1
  197. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/resource-reference.js +225 -10
  198. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/resource-reference.js.map +1 -1
  199. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/schema/pod-table.d.ts +13 -0
  200. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/schema/pod-table.d.ts.map +1 -1
  201. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/schema/pod-table.js +19 -0
  202. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/schema/pod-table.js.map +1 -1
  203. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/index.d.ts +5 -4
  204. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/index.d.ts.map +1 -1
  205. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/index.js +4 -3
  206. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/index.js.map +1 -1
  207. package/node_modules/@undefineds.co/drizzle-solid/dist/index.d.ts +5 -4
  208. package/node_modules/@undefineds.co/drizzle-solid/dist/index.d.ts.map +1 -1
  209. package/node_modules/@undefineds.co/drizzle-solid/dist/index.js +20 -3
  210. package/node_modules/@undefineds.co/drizzle-solid/dist/index.js.map +1 -1
  211. package/node_modules/@undefineds.co/drizzle-solid/package.json +1 -1
  212. package/package.json +3 -3
  213. package/dist/api/handlers/ApiKeyHandler.d.ts +0 -15
  214. package/dist/api/handlers/ApiKeyHandler.js +0 -153
  215. package/dist/api/handlers/ApiKeyHandler.js.map +0 -1
  216. package/dist/api/store/DrizzleClientCredentialsStore.d.ts +0 -51
  217. package/dist/api/store/DrizzleClientCredentialsStore.js +0 -115
  218. package/dist/api/store/DrizzleClientCredentialsStore.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ClusterWebSocketConfigurator.js","sourceRoot":"","sources":["../../src/http/ClusterWebSocketConfigurator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, Server } from 'node:http';\nimport type { Duplex } from 'node:stream';\nimport httpProxy from 'http-proxy';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface ClusterWebSocketConfiguratorOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n clusterIngressDomain: string;\n}\n\n/**\n * ServerConfigurator that handles WebSocket upgrade requests for edge nodes.\n * \n * For proxy mode: proxies WebSocket connections through FRP tunnel\n * For direct mode: sends 307 redirect to edge node's public IP\n */\nexport class ClusterWebSocketConfigurator {\n protected readonly logger = getLoggerFor(this);\n private readonly repository: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly clusterIngressDomain: string;\n private readonly wsProxy: httpProxy;\n\n public constructor(options: ClusterWebSocketConfiguratorOptions) {\n this.repository = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.clusterIngressDomain = this.normalizeDomain(options.clusterIngressDomain);\n \n // Create WebSocket proxy instance\n this.wsProxy = httpProxy.createProxyServer({\n ws: true,\n changeOrigin: true,\n xfwd: true,\n });\n \n this.wsProxy.on('error', (err, req, res) => {\n this.logger.error(`WebSocket proxy error: ${String(err)}`);\n if (res && 'end' in res && typeof res.end === 'function') {\n res.end();\n }\n });\n }\n\n /**\n * Attach to HTTP server's upgrade event\n */\n public async handle(server: Server): Promise<void> {\n if (!this.enabled) {\n this.logger.info('ClusterWebSocketConfigurator disabled');\n return;\n }\n\n // Prepend our handler to run before CSS's WebSocketServerConfigurator\n server.prependListener('upgrade', (request: IncomingMessage, socket: Duplex, head: Buffer) => {\n this.handleUpgrade(request, socket, head).catch((error) => {\n this.logger.error(`WebSocket upgrade error: ${String(error)}`);\n // Don't destroy socket here - let other handlers try\n });\n });\n\n this.logger.info('ClusterWebSocketConfigurator attached to server');\n }\n\n /**\n * Handle WebSocket upgrade request\n */\n private async handleUpgrade(\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer\n ): Promise<boolean> {\n const hostname = this.extractHostname(request);\n if (!hostname) {\n return false; // Let other handlers deal with it\n }\n\n // Only handle requests to node subdomains\n if (hostname === this.clusterIngressDomain) {\n return false; // Cluster domain - let CSS handle it\n }\n\n const nodeId = this.extractNodeIdFromHostname(hostname);\n if (!nodeId) {\n return false; // Not a node subdomain\n }\n\n // Verify node exists\n const nodeSecret = await this.repository.getNodeSecret(nodeId);\n if (!nodeSecret) {\n this.logger.warn(`WebSocket upgrade: Node ${nodeId} not registered`);\n this.sendUpgradeError(socket, 404, `Node ${nodeId} not found`);\n return true; // We handled it (with error)\n }\n\n // Get node info\n const [nodeInfo, nodeMetadata] = await Promise.all([\n this.repository.getNodeConnectivityInfo(nodeId),\n this.repository.getNodeMetadata(nodeId),\n ]);\n\n if (!nodeInfo) {\n this.logger.error(`WebSocket upgrade: Node ${nodeId} connectivity info not found`);\n this.sendUpgradeError(socket, 502, 'Node connectivity info not found');\n return true;\n }\n\n const mode = this.normalizeMode(nodeInfo.accessMode);\n\n if (mode === 'direct' && nodeInfo.ipv4) {\n // Direct mode: redirect client to connect directly\n const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';\n const directUrl = `wss://${nodeInfo.ipv4}${port}${request.url ?? '/'}`;\n\n this.logger.info(`WebSocket direct mode: redirecting to ${directUrl}`);\n \n socket.write(\n `HTTP/1.1 307 Temporary Redirect\\r\\n` +\n `Location: ${directUrl}\\r\\n` +\n `X-Xpod-Direct-Node: ${nodeId}\\r\\n` +\n `Connection: close\\r\\n` +\n `\\r\\n`\n );\n socket.end();\n return true;\n }\n\n if (mode === 'proxy') {\n const upstream = this.resolveUpstream(nodeMetadata?.metadata || null);\n if (!upstream) {\n this.logger.error(`WebSocket upgrade: Node ${nodeId} tunnel endpoint not ready`);\n this.sendUpgradeError(socket, 502, 'Node tunnel not ready');\n return true;\n }\n\n const upstreamUrl = new URL(upstream);\n const wsProtocol = upstreamUrl.protocol === 'https:' ? 'wss:' : 'ws:';\n const target = `${wsProtocol}//${upstreamUrl.host}`;\n\n this.logger.info(`WebSocket proxy: ${hostname} -> ${target}${request.url}`);\n\n // Add forwarded headers\n request.headers['x-forwarded-host'] = hostname;\n request.headers['x-forwarded-proto'] = 'wss';\n request.headers['x-xpod-proxy-node'] = nodeId;\n\n // Proxy the WebSocket connection\n this.wsProxy.ws(request, socket, head, {\n target,\n secure: true,\n });\n return true;\n }\n\n this.logger.warn(`WebSocket upgrade: Unsupported mode ${mode} for node ${nodeId}`);\n this.sendUpgradeError(socket, 400, `Unsupported access mode: ${mode}`);\n return true;\n }\n\n /**\n * Send HTTP error response for WebSocket upgrade failure\n */\n private sendUpgradeError(socket: Duplex, statusCode: number, message: string): void {\n socket.write(\n `HTTP/1.1 ${statusCode} ${message}\\r\\n` +\n `Content-Type: text/plain\\r\\n` +\n `Connection: close\\r\\n` +\n `\\r\\n` +\n message\n );\n socket.end();\n }\n\n /**\n * Resolve upstream endpoint from node metadata\n */\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n if (typeof metadata?.publicAddress === 'string') {\n return metadata.publicAddress;\n }\n return undefined;\n }\n\n /**\n * Extract hostname from request headers\n */\n private extractHostname(request: IncomingMessage): string | undefined {\n const originalHost = request.headers['x-original-host'];\n if (originalHost && typeof originalHost === 'string') {\n return originalHost.toLowerCase();\n }\n const hostHeader = request.headers.host || request.headers.Host;\n if (Array.isArray(hostHeader)) {\n return hostHeader[0]?.toLowerCase();\n }\n return typeof hostHeader === 'string' ? hostHeader.toLowerCase() : undefined;\n }\n\n /**\n * Extract node ID from hostname\n */\n private extractNodeIdFromHostname(hostname: string): string | undefined {\n const clusterSuffix = `.${this.clusterIngressDomain}`;\n if (!hostname.endsWith(clusterSuffix)) {\n return undefined;\n }\n const nodeId = hostname.slice(0, -clusterSuffix.length);\n if (!nodeId || nodeId.includes('.') || nodeId.length === 0) {\n return undefined;\n }\n return nodeId;\n }\n\n /**\n * Normalize domain input\n */\n private normalizeDomain(domain: string): string {\n if (domain.includes('://')) {\n try {\n return new URL(domain).hostname.toLowerCase();\n } catch {\n return domain.toLowerCase();\n }\n }\n return domain.toLowerCase();\n }\n\n /**\n * Normalize boolean values\n */\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n /**\n * Normalize access mode\n */\n private normalizeMode(mode: string | undefined): 'direct' | 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n if (normalized === 'redirect' || normalized === 'direct') {\n return 'direct';\n }\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"ClusterWebSocketConfigurator.js","sourceRoot":"","sources":["../../src/http/ClusterWebSocketConfigurator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, Server } from 'node:http';\nimport type { Duplex } from 'node:stream';\nimport httpProxy from 'http-proxy';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface ClusterWebSocketConfiguratorOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n clusterIngressDomain: string;\n}\n\n/**\n * ServerConfigurator that handles WebSocket upgrade requests for edge nodes.\n * \n * For proxy mode: proxies WebSocket connections through FRP tunnel\n * For direct mode: sends 307 redirect to edge node's public IP\n */\nexport class ClusterWebSocketConfigurator {\n protected readonly logger = getLoggerFor(this);\n private readonly repository: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly clusterIngressDomain: string;\n private readonly wsProxy: httpProxy;\n\n public constructor(options: ClusterWebSocketConfiguratorOptions) {\n this.repository = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.clusterIngressDomain = this.normalizeDomain(options.clusterIngressDomain);\n \n // Create WebSocket proxy instance\n this.wsProxy = httpProxy.createProxyServer({\n ws: true,\n changeOrigin: true,\n xfwd: true,\n });\n \n this.wsProxy.on('error', (err, req, res) => {\n this.logger.error(`WebSocket proxy error: ${String(err)}`);\n if (res && 'end' in res && typeof res.end === 'function') {\n res.end();\n }\n });\n }\n\n /**\n * Attach to HTTP server's upgrade event\n */\n public async handle(server: Server): Promise<void> {\n if (!this.enabled) {\n this.logger.info('ClusterWebSocketConfigurator disabled');\n return;\n }\n\n // Prepend our handler to run before CSS's WebSocketServerConfigurator\n server.prependListener('upgrade', (request: IncomingMessage, socket: Duplex, head: Buffer) => {\n this.handleUpgrade(request, socket, head).catch((error) => {\n this.logger.error(`WebSocket upgrade error: ${String(error)}`);\n // Don't destroy socket here - let other handlers try\n });\n });\n\n this.logger.info('ClusterWebSocketConfigurator attached to server');\n }\n\n /**\n * Handle WebSocket upgrade request\n */\n private async handleUpgrade(\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer\n ): Promise<boolean> {\n const hostname = this.extractHostname(request);\n if (!hostname) {\n return false; // Let other handlers deal with it\n }\n\n // Only handle requests to node subdomains\n if (hostname === this.clusterIngressDomain) {\n return false; // Cluster domain - let CSS handle it\n }\n\n const nodeId = this.extractNodeIdFromHostname(hostname);\n if (!nodeId) {\n return false; // Not a node subdomain\n }\n\n // Verify node exists\n const nodeSecret = await this.repository.getNodeSecret(nodeId);\n if (!nodeSecret) {\n this.logger.warn(`WebSocket upgrade: Node ${nodeId} not registered`);\n this.sendUpgradeError(socket, 404, `Node ${nodeId} not found`);\n return true; // We handled it (with error)\n }\n\n // Get node info\n const [nodeInfo, nodeMetadata] = await Promise.all([\n this.repository.getNodeConnectivityInfo(nodeId),\n this.repository.getNodeMetadata(nodeId),\n ]);\n\n if (!nodeInfo) {\n this.logger.error(`WebSocket upgrade: Node ${nodeId} connectivity info not found`);\n this.sendUpgradeError(socket, 502, 'Node connectivity info not found');\n return true;\n }\n\n const mode = this.normalizeMode(nodeInfo.accessMode);\n\n if (mode === 'direct' && nodeInfo.ipv4) {\n // Direct mode: redirect client to connect directly\n const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';\n const directUrl = `wss://${nodeInfo.ipv4}${port}${request.url ?? '/'}`;\n\n this.logger.info(`WebSocket direct mode: redirecting to ${directUrl}`);\n \n socket.write(\n `HTTP/1.1 307 Temporary Redirect\\r\\n` +\n `Location: ${directUrl}\\r\\n` +\n `X-Xpod-Direct-Node: ${nodeId}\\r\\n` +\n `Connection: close\\r\\n` +\n `\\r\\n`\n );\n socket.end();\n return true;\n }\n\n if (mode === 'proxy') {\n const upstream = this.resolveUpstream(nodeMetadata?.metadata || null);\n if (!upstream) {\n this.logger.error(`WebSocket upgrade: Node ${nodeId} tunnel endpoint not ready`);\n this.sendUpgradeError(socket, 502, 'Node tunnel not ready');\n return true;\n }\n\n const upstreamUrl = new URL(upstream);\n const wsProtocol = upstreamUrl.protocol === 'https:' ? 'wss:' : 'ws:';\n const target = `${wsProtocol}//${upstreamUrl.host}`;\n\n this.logger.info(`WebSocket proxy: ${hostname} -> ${target}${request.url}`);\n\n // Add forwarded headers\n request.headers['x-forwarded-host'] = hostname;\n request.headers['x-forwarded-proto'] = 'wss';\n request.headers['x-xpod-proxy-node'] = nodeId;\n\n // Proxy the WebSocket connection\n this.wsProxy.ws(request, socket, head, {\n target,\n secure: true,\n });\n return true;\n }\n\n this.logger.warn(`WebSocket upgrade: Unsupported mode ${mode} for node ${nodeId}`);\n this.sendUpgradeError(socket, 400, `Unsupported access mode: ${mode}`);\n return true;\n }\n\n /**\n * Send HTTP error response for WebSocket upgrade failure\n */\n private sendUpgradeError(socket: Duplex, statusCode: number, message: string): void {\n socket.write(\n `HTTP/1.1 ${statusCode} ${message}\\r\\n` +\n `Content-Type: text/plain\\r\\n` +\n `Connection: close\\r\\n` +\n `\\r\\n` +\n message\n );\n socket.end();\n }\n\n /**\n * Resolve upstream endpoint from node metadata\n */\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n const managedTunnel = metadata?.managedTunnel;\n if (managedTunnel && typeof managedTunnel === 'object') {\n const endpoint = (managedTunnel as Record<string, unknown>).endpoint;\n if (typeof endpoint === 'string' && endpoint.trim().length > 0) {\n return endpoint;\n }\n }\n return undefined;\n }\n\n /**\n * Extract hostname from request headers\n */\n private extractHostname(request: IncomingMessage): string | undefined {\n const originalHost = request.headers['x-original-host'];\n if (originalHost && typeof originalHost === 'string') {\n return originalHost.toLowerCase();\n }\n const hostHeader = request.headers.host || request.headers.Host;\n if (Array.isArray(hostHeader)) {\n return hostHeader[0]?.toLowerCase();\n }\n return typeof hostHeader === 'string' ? hostHeader.toLowerCase() : undefined;\n }\n\n /**\n * Extract node ID from hostname\n */\n private extractNodeIdFromHostname(hostname: string): string | undefined {\n const clusterSuffix = `.${this.clusterIngressDomain}`;\n if (!hostname.endsWith(clusterSuffix)) {\n return undefined;\n }\n const nodeId = hostname.slice(0, -clusterSuffix.length);\n if (!nodeId || nodeId.includes('.') || nodeId.length === 0) {\n return undefined;\n }\n return nodeId;\n }\n\n /**\n * Normalize domain input\n */\n private normalizeDomain(domain: string): string {\n if (domain.includes('://')) {\n try {\n return new URL(domain).hostname.toLowerCase();\n } catch {\n return domain.toLowerCase();\n }\n }\n return domain.toLowerCase();\n }\n\n /**\n * Normalize boolean values\n */\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n /**\n * Normalize access mode\n */\n private normalizeMode(mode: string | undefined): 'direct' | 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n if (normalized === 'redirect' || normalized === 'direct') {\n return 'direct';\n }\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
@@ -18,6 +18,8 @@ export declare class EdgeNodeDirectDebugHttpHandler extends HttpHandler {
18
18
  private resolve;
19
19
  private shouldSkip;
20
20
  private resolveTarget;
21
+ private extractTunnelEntrypoint;
22
+ private extractManagedTunnelEndpoint;
21
23
  private extractUrl;
22
24
  private parseUrl;
23
25
  private normalizeBoolean;
@@ -59,7 +59,7 @@ class EdgeNodeDirectDebugHttpHandler extends community_server_1.HttpHandler {
59
59
  this.logger.warn(`Request reached cluster for proxy-mode node ${record.nodeId}, should be handled by L4 SNI proxy`);
60
60
  return undefined;
61
61
  }
62
- const target = this.resolveTarget(record.metadata ?? {}, original, record.baseUrl);
62
+ const target = this.resolveTarget(record.metadata ?? {}, original, record.baseUrl, record.publicUrl);
63
63
  if (!target) {
64
64
  return undefined;
65
65
  }
@@ -68,8 +68,11 @@ class EdgeNodeDirectDebugHttpHandler extends community_server_1.HttpHandler {
68
68
  shouldSkip(pathname) {
69
69
  return this.skipPrefixes.some((prefix) => pathname.startsWith(prefix));
70
70
  }
71
- resolveTarget(metadata, original, podBaseUrl) {
72
- const baseUrl = this.extractUrl(metadata.publicAddress) ?? this.extractUrl(metadata.baseUrl);
71
+ resolveTarget(metadata, original, podBaseUrl, publicUrl) {
72
+ const baseUrl = this.extractUrl(publicUrl)
73
+ ?? this.extractUrl(this.extractTunnelEntrypoint(metadata.tunnel))
74
+ ?? this.extractUrl(this.extractManagedTunnelEndpoint(metadata.managedTunnel))
75
+ ?? this.extractUrl(metadata.baseUrl);
73
76
  if (!baseUrl) {
74
77
  return undefined;
75
78
  }
@@ -88,6 +91,18 @@ class EdgeNodeDirectDebugHttpHandler extends community_server_1.HttpHandler {
88
91
  target.hash = original.hash;
89
92
  return target;
90
93
  }
94
+ extractTunnelEntrypoint(value) {
95
+ if (!value || typeof value !== 'object') {
96
+ return undefined;
97
+ }
98
+ return value.entrypoint;
99
+ }
100
+ extractManagedTunnelEndpoint(value) {
101
+ if (!value || typeof value !== 'object') {
102
+ return undefined;
103
+ }
104
+ return value.endpoint;
105
+ }
91
106
  extractUrl(value) {
92
107
  if (typeof value !== 'string') {
93
108
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"EdgeNodeDirectDebugHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeDirectDebugHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeDirectDebugHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n skipPrefixes?: string[];\n nodeRepository?: EdgeNodeRepository;\n}\n\nexport class EdgeNodeDirectDebugHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly nodeRepo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly skipPrefixes: string[];\n\n public constructor(options: EdgeNodeDirectDebugHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.nodeRepo = options.nodeRepository ?? new EdgeNodeRepository(db as any);\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.skipPrefixes = options.skipPrefixes ?? [ '/admin', '/api', '/.internal' ];\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge node redirection disabled.');\n }\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const target = await this.resolve(url.pathname, url);\n if (!target) {\n throw new NotImplementedHttpError('Edge node endpoint not configured.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const resolved = await this.resolve(url.pathname, url);\n if (!resolved) {\n throw new NotImplementedHttpError('No edge node registered for this resource.');\n }\n\n this.logger.debug(`Redirecting ${url.pathname} to edge node ${resolved.nodeId}`);\n try {\n response.statusCode = 307;\n response.setHeader('Location', resolved.target.toString());\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('X-Xpod-Edge-Node', resolved.nodeId);\n response.end();\n } catch (error: unknown) {\n throw new InternalServerError('Failed to write edge node redirect.', { cause: error });\n }\n }\n\n private async resolve(pathname: string, original: URL): Promise<{ nodeId: string; target: URL } | undefined> {\n const record = await this.nodeRepo.findNodeByResourcePath(pathname);\n if (!record) {\n return undefined;\n }\n \n // Only handle direct mode nodes - proxy mode traffic should go through L4 SNI proxy\n if (record.accessMode === 'proxy') {\n this.logger.warn(`Request reached cluster for proxy-mode node ${record.nodeId}, should be handled by L4 SNI proxy`);\n return undefined;\n }\n \n const target = this.resolveTarget(record.metadata ?? {}, original, record.baseUrl);\n if (!target) {\n return undefined;\n }\n return { nodeId: record.nodeId, target };\n }\n\n private shouldSkip(pathname: string): boolean {\n return this.skipPrefixes.some((prefix) => pathname.startsWith(prefix));\n }\n\n private resolveTarget(metadata: Record<string, unknown>, original: URL, podBaseUrl: string): URL | undefined {\n const baseUrl = this.extractUrl(metadata.publicAddress) ?? this.extractUrl(metadata.baseUrl);\n if (!baseUrl) {\n return undefined;\n }\n const podBase = this.extractUrl(podBaseUrl);\n if (!podBase) {\n return undefined;\n }\n const podPath = podBase.pathname.endsWith('/') ? podBase.pathname : `${podBase.pathname}/`;\n if (!original.pathname.startsWith(podPath)) {\n return undefined;\n }\n const relativePath = original.pathname.slice(podPath.length);\n const targetBase = baseUrl.href.endsWith('/') ? baseUrl.href : `${baseUrl.href}/`;\n const target = new URL(relativePath, targetBase);\n target.search = original.search;\n target.hash = original.hash;\n return target;\n }\n\n private extractUrl(value: unknown): URL | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n return undefined;\n }\n try {\n return new URL(trimmed);\n } catch {\n return undefined;\n }\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"EdgeNodeDirectDebugHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeDirectDebugHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeDirectDebugHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n skipPrefixes?: string[];\n nodeRepository?: EdgeNodeRepository;\n}\n\nexport class EdgeNodeDirectDebugHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly nodeRepo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly skipPrefixes: string[];\n\n public constructor(options: EdgeNodeDirectDebugHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.nodeRepo = options.nodeRepository ?? new EdgeNodeRepository(db as any);\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.skipPrefixes = options.skipPrefixes ?? [ '/admin', '/api', '/.internal' ];\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge node redirection disabled.');\n }\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const target = await this.resolve(url.pathname, url);\n if (!target) {\n throw new NotImplementedHttpError('Edge node endpoint not configured.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n if (this.shouldSkip(url.pathname)) {\n throw new NotImplementedHttpError('Path excluded from edge routing.');\n }\n const resolved = await this.resolve(url.pathname, url);\n if (!resolved) {\n throw new NotImplementedHttpError('No edge node registered for this resource.');\n }\n\n this.logger.debug(`Redirecting ${url.pathname} to edge node ${resolved.nodeId}`);\n try {\n response.statusCode = 307;\n response.setHeader('Location', resolved.target.toString());\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('X-Xpod-Edge-Node', resolved.nodeId);\n response.end();\n } catch (error: unknown) {\n throw new InternalServerError('Failed to write edge node redirect.', { cause: error });\n }\n }\n\n private async resolve(pathname: string, original: URL): Promise<{ nodeId: string; target: URL } | undefined> {\n const record = await this.nodeRepo.findNodeByResourcePath(pathname);\n if (!record) {\n return undefined;\n }\n \n // Only handle direct mode nodes - proxy mode traffic should go through L4 SNI proxy\n if (record.accessMode === 'proxy') {\n this.logger.warn(`Request reached cluster for proxy-mode node ${record.nodeId}, should be handled by L4 SNI proxy`);\n return undefined;\n }\n \n const target = this.resolveTarget(record.metadata ?? {}, original, record.baseUrl, record.publicUrl);\n if (!target) {\n return undefined;\n }\n return { nodeId: record.nodeId, target };\n }\n\n private shouldSkip(pathname: string): boolean {\n return this.skipPrefixes.some((prefix) => pathname.startsWith(prefix));\n }\n\n private resolveTarget(metadata: Record<string, unknown>, original: URL, podBaseUrl: string, publicUrl?: string): URL | undefined {\n const baseUrl = this.extractUrl(publicUrl)\n ?? this.extractUrl(this.extractTunnelEntrypoint(metadata.tunnel))\n ?? this.extractUrl(this.extractManagedTunnelEndpoint(metadata.managedTunnel))\n ?? this.extractUrl(metadata.baseUrl);\n if (!baseUrl) {\n return undefined;\n }\n const podBase = this.extractUrl(podBaseUrl);\n if (!podBase) {\n return undefined;\n }\n const podPath = podBase.pathname.endsWith('/') ? podBase.pathname : `${podBase.pathname}/`;\n if (!original.pathname.startsWith(podPath)) {\n return undefined;\n }\n const relativePath = original.pathname.slice(podPath.length);\n const targetBase = baseUrl.href.endsWith('/') ? baseUrl.href : `${baseUrl.href}/`;\n const target = new URL(relativePath, targetBase);\n target.search = original.search;\n target.hash = original.hash;\n return target;\n }\n\n private extractTunnelEntrypoint(value: unknown): unknown {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n return (value as Record<string, unknown>).entrypoint;\n }\n\n private extractManagedTunnelEndpoint(value: unknown): unknown {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n return (value as Record<string, unknown>).endpoint;\n }\n\n private extractUrl(value: unknown): URL | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n return undefined;\n }\n try {\n return new URL(trimmed);\n } catch {\n return undefined;\n }\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n}\n"]}
@@ -102,6 +102,14 @@
102
102
  "@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_resolveTarget",
103
103
  "memberFieldName": "resolveTarget"
104
104
  },
105
+ {
106
+ "@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_extractTunnelEntrypoint",
107
+ "memberFieldName": "extractTunnelEntrypoint"
108
+ },
109
+ {
110
+ "@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_extractManagedTunnelEndpoint",
111
+ "memberFieldName": "extractManagedTunnelEndpoint"
112
+ },
105
113
  {
106
114
  "@id": "undefineds:dist/http/EdgeNodeDirectDebugHttpHandler.jsonld#EdgeNodeDirectDebugHttpHandler__member_extractUrl",
107
115
  "memberFieldName": "extractUrl"
@@ -99,8 +99,12 @@ class EdgeNodeProxyHttpHandler extends community_server_1.HttpHandler {
99
99
  return entrypoint;
100
100
  }
101
101
  }
102
- if (typeof metadata?.publicAddress === 'string') {
103
- return metadata.publicAddress;
102
+ const managedTunnel = metadata?.managedTunnel;
103
+ if (managedTunnel && typeof managedTunnel === 'object') {
104
+ const endpoint = managedTunnel.endpoint;
105
+ if (typeof endpoint === 'string' && endpoint.trim().length > 0) {
106
+ return endpoint;
107
+ }
104
108
  }
105
109
  return undefined;
106
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EdgeNodeProxyHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeProxyHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { Readable } from 'node:stream';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n BadRequestHttpError,\n InternalServerError,\n MethodNotAllowedHttpError,\n NotImplementedHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeProxyHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n fetchImpl?: any;\n}\n\ninterface ProxyNode {\n nodeId: string;\n metadata?: Record<string, unknown> | null;\n}\n\nexport class EdgeNodeProxyHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly repo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly fetchImpl: any;\n\n public constructor(options: EdgeNodeProxyHttpHandlerOptions) {\n super();\n this.repo = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge proxy disabled.');\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new NotImplementedHttpError('Missing Host header.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n if ((request.method ?? 'GET').toUpperCase() === 'OPTIONS') {\n this.writeOptions(response);\n return;\n }\n if ((request.method ?? 'GET').toUpperCase() === 'CONNECT') {\n throw new MethodNotAllowedHttpError([ 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS' ]);\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new BadRequestHttpError('Host header required.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n await this.forwardRequest(node, request, response);\n }\n\n private async forwardRequest(node: ProxyNode, request: IncomingMessage, response: HttpResponse): Promise<void> {\n const metadata = node.metadata ?? undefined;\n const upstream = this.resolveUpstream(metadata);\n if (!upstream) {\n throw new InternalServerError('Edge node tunnel is not ready.');\n }\n\n const upstreamBase = new URL(upstream);\n const originalUrl = this.parseUrl(request);\n const target = new URL(originalUrl.pathname + originalUrl.search, upstreamBase);\n target.hash = originalUrl.hash;\n\n const body = await this.readBody(request);\n const headers = this.buildHeaders(request, originalUrl, upstreamBase);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await this.fetchImpl(target.toString(), {\n method: (request.method ?? 'GET').toUpperCase(),\n headers,\n body: body?.length ? body : undefined,\n });\n } catch (error: unknown) {\n this.logger.error(`Proxy request to ${target.toString()} failed: ${(error as Error).message}`);\n throw new InternalServerError('Failed to proxy edge node request.', { cause: error });\n }\n\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Edge-Node', node.nodeId);\n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return;\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n const readable = Readable.from(upstreamResponse.body as any);\n readable.on('error', (error) => {\n this.logger.error(`Edge proxy stream error: ${(error as Error).message}`);\n response.destroy(error as Error);\n });\n readable.pipe(response);\n }\n\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n if (typeof metadata?.publicAddress === 'string') {\n return metadata.publicAddress;\n }\n return undefined;\n }\n\n private buildHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n for (const [ name, value ] of Object.entries(request.headers)) {\n if (value === undefined) {\n continue;\n }\n if (name.toLowerCase() === 'host') {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(name, value.join(','));\n } else {\n headers.set(name, value);\n }\n }\n headers.set('host', upstream.host);\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n const remoteAddress = (request.socket as any)?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n return headers;\n }\n\n private readBody(request: IncomingMessage): Promise<Buffer | undefined> {\n const method = (request.method ?? 'GET').toUpperCase();\n if ([ 'GET', 'HEAD' ].includes(method)) {\n return Promise.resolve(undefined);\n }\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => resolve(Buffer.concat(chunks)));\n request.on('error', reject);\n });\n }\n\n private extractHostname(request: IncomingMessage): string | undefined {\n const hostHeader = request.headers.host ?? request.headers.Host;\n if (typeof hostHeader !== 'string') {\n return undefined;\n }\n const hostname = hostHeader.split(':')[0];\n return hostname.trim().toLowerCase();\n }\n\n private writeOptions(response: HttpResponse): void {\n response.statusCode = 204;\n response.setHeader('Allow', 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS');\n response.end();\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n private normalizeMode(mode: string | undefined): 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"EdgeNodeProxyHttpHandler.js","sourceRoot":"","sources":["../../src/http/EdgeNodeProxyHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { Readable } from 'node:stream';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n BadRequestHttpError,\n InternalServerError,\n MethodNotAllowedHttpError,\n NotImplementedHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface EdgeNodeProxyHttpHandlerOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n fetchImpl?: any;\n}\n\ninterface ProxyNode {\n nodeId: string;\n metadata?: Record<string, unknown> | null;\n}\n\nexport class EdgeNodeProxyHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly repo: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly fetchImpl: any;\n\n public constructor(options: EdgeNodeProxyHttpHandlerOptions) {\n super();\n this.repo = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Edge proxy disabled.');\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new NotImplementedHttpError('Missing Host header.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n if ((request.method ?? 'GET').toUpperCase() === 'OPTIONS') {\n this.writeOptions(response);\n return;\n }\n if ((request.method ?? 'GET').toUpperCase() === 'CONNECT') {\n throw new MethodNotAllowedHttpError([ 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS' ]);\n }\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new BadRequestHttpError('Host header required.');\n }\n const node = await this.repo.findNodeBySubdomain(hostname);\n const mode = this.normalizeMode(node?.accessMode);\n if (!node || mode !== 'proxy') {\n throw new NotImplementedHttpError('Not a proxy edge node request.');\n }\n await this.forwardRequest(node, request, response);\n }\n\n private async forwardRequest(node: ProxyNode, request: IncomingMessage, response: HttpResponse): Promise<void> {\n const metadata = node.metadata ?? undefined;\n const upstream = this.resolveUpstream(metadata);\n if (!upstream) {\n throw new InternalServerError('Edge node tunnel is not ready.');\n }\n\n const upstreamBase = new URL(upstream);\n const originalUrl = this.parseUrl(request);\n const target = new URL(originalUrl.pathname + originalUrl.search, upstreamBase);\n target.hash = originalUrl.hash;\n\n const body = await this.readBody(request);\n const headers = this.buildHeaders(request, originalUrl, upstreamBase);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await this.fetchImpl(target.toString(), {\n method: (request.method ?? 'GET').toUpperCase(),\n headers,\n body: body?.length ? body : undefined,\n });\n } catch (error: unknown) {\n this.logger.error(`Proxy request to ${target.toString()} failed: ${(error as Error).message}`);\n throw new InternalServerError('Failed to proxy edge node request.', { cause: error });\n }\n\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Edge-Node', node.nodeId);\n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return;\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n const readable = Readable.from(upstreamResponse.body as any);\n readable.on('error', (error) => {\n this.logger.error(`Edge proxy stream error: ${(error as Error).message}`);\n response.destroy(error as Error);\n });\n readable.pipe(response);\n }\n\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n const managedTunnel = metadata?.managedTunnel;\n if (managedTunnel && typeof managedTunnel === 'object') {\n const endpoint = (managedTunnel as Record<string, unknown>).endpoint;\n if (typeof endpoint === 'string' && endpoint.trim().length > 0) {\n return endpoint;\n }\n }\n return undefined;\n }\n\n private buildHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n for (const [ name, value ] of Object.entries(request.headers)) {\n if (value === undefined) {\n continue;\n }\n if (name.toLowerCase() === 'host') {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(name, value.join(','));\n } else {\n headers.set(name, value);\n }\n }\n headers.set('host', upstream.host);\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n const remoteAddress = (request.socket as any)?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n return headers;\n }\n\n private readBody(request: IncomingMessage): Promise<Buffer | undefined> {\n const method = (request.method ?? 'GET').toUpperCase();\n if ([ 'GET', 'HEAD' ].includes(method)) {\n return Promise.resolve(undefined);\n }\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => resolve(Buffer.concat(chunks)));\n request.on('error', reject);\n });\n }\n\n private extractHostname(request: IncomingMessage): string | undefined {\n const hostHeader = request.headers.host ?? request.headers.Host;\n if (typeof hostHeader !== 'string') {\n return undefined;\n }\n const hostname = hostHeader.split(':')[0];\n return hostname.trim().toLowerCase();\n }\n\n private writeOptions(response: HttpResponse): void {\n response.statusCode = 204;\n response.setHeader('Allow', 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS');\n response.end();\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'http' : 'http';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n private normalizeMode(mode: string | undefined): 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
@@ -9,7 +9,7 @@ interface PodMigrationHttpHandlerOptions {
9
9
  /**
10
10
  * HTTP Handler for Pod migration operations.
11
11
  *
12
- * Migration is now instant - it only updates the nodeId in the database.
12
+ * Migration is now instant - it only updates the Pod routing assignment.
13
13
  * Binary files are accessed via presigned URL redirect (302) from object storage.
14
14
  *
15
15
  * Endpoints:
@@ -38,7 +38,7 @@ export declare class PodMigrationHttpHandler extends HttpHandler {
38
38
  private handleGetPod;
39
39
  /**
40
40
  * Migrate pod to target node.
41
- * This is instant - only updates nodeId. Binary files use cross-region fallback.
41
+ * This is instant - only updates routing ownership. Binary files use cross-region fallback.
42
42
  */
43
43
  private handleMigrate;
44
44
  private matchesPath;
@@ -11,7 +11,7 @@ const PodMigrationService_1 = require("../../service/PodMigrationService");
11
11
  /**
12
12
  * HTTP Handler for Pod migration operations.
13
13
  *
14
- * Migration is now instant - it only updates the nodeId in the database.
14
+ * Migration is now instant - it only updates the Pod routing assignment.
15
15
  * Binary files are accessed via presigned URL redirect (302) from object storage.
16
16
  *
17
17
  * Endpoints:
@@ -115,7 +115,7 @@ class PodMigrationHttpHandler extends community_server_1.HttpHandler {
115
115
  }
116
116
  /**
117
117
  * Migrate pod to target node.
118
- * This is instant - only updates nodeId. Binary files use cross-region fallback.
118
+ * This is instant - only updates routing ownership. Binary files use cross-region fallback.
119
119
  */
120
120
  async handleMigrate(podId, body, response) {
121
121
  if (!body.targetNode) {
@@ -1 +1 @@
1
- {"version":3,"file":"PodMigrationHttpHandler.js","sourceRoot":"","sources":["../../../src/http/cluster/PodMigrationHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n BadRequestHttpError,\n NotFoundHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../../identity/drizzle/db';\nimport { PodLookupRepository } from '../../identity/drizzle/PodLookupRepository';\nimport { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { PodMigrationService } from '../../service/PodMigrationService';\n\ninterface PodMigrationHttpHandlerOptions {\n identityDbUrl: string;\n currentNodeId: string;\n basePath?: string;\n enabled?: boolean | string;\n}\n\ninterface MigrateRequest {\n targetNode: string;\n}\n\n/**\n * HTTP Handler for Pod migration operations.\n * \n * Migration is now instant - it only updates the nodeId in the database.\n * Binary files are accessed via presigned URL redirect (302) from object storage.\n * \n * Endpoints:\n * - POST /.cluster/pods/{podId}/migrate - Migrate pod to target node (instant)\n * - GET /.cluster/pods/{podId} - Get pod info\n * - GET /.cluster/pods - List all pods with node info\n */\nexport class PodMigrationHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n \n private readonly podLookupRepository: PodLookupRepository;\n private readonly edgeNodeRepository: EdgeNodeRepository;\n private readonly migrationService: PodMigrationService;\n private readonly basePath: string;\n private readonly basePathWithSlash: string;\n private readonly enabled: boolean;\n\n public constructor(options: PodMigrationHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.podLookupRepository = new PodLookupRepository(db);\n this.edgeNodeRepository = new EdgeNodeRepository(db);\n this.migrationService = new PodMigrationService({\n identityDbUrl: options.identityDbUrl,\n currentNodeId: options.currentNodeId,\n });\n this.basePath = this.normalizeBasePath(options.basePath ?? '/.cluster/pods');\n this.basePathWithSlash = `${this.basePath}/`;\n this.enabled = this.normalizeBoolean(options.enabled);\n\n this.logger.info(`PodMigrationHttpHandler initialized: basePath=${this.basePath}, enabled=${this.enabled}`);\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Pod migration API disabled.');\n }\n\n const url = this.parseUrl(request);\n if (!this.matchesPath(url.pathname)) {\n throw new NotImplementedHttpError(`Path ${url.pathname} does not match ${this.basePath}`);\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n const method = (request.method ?? 'GET').toUpperCase();\n const relativePath = this.getRelativePath(url.pathname);\n\n try {\n // GET /.cluster/pods - List pods\n if (relativePath === '' && method === 'GET') {\n await this.handleListPods(response);\n return;\n }\n\n // Parse pod ID and action\n const match = relativePath.match(/^([^/]+)(?:\\/(.+))?$/);\n if (!match) {\n throw new BadRequestHttpError('Invalid path format. Expected: /pods/{podId} or /pods/{podId}/migrate');\n }\n\n const podId = decodeURIComponent(match[1]);\n const action = match[2];\n\n // GET /.cluster/pods/{podId} - Get pod info\n if (!action && method === 'GET') {\n await this.handleGetPod(podId, response);\n return;\n }\n\n // POST /.cluster/pods/{podId}/migrate - Migrate pod (instant)\n if (action === 'migrate' && method === 'POST') {\n const body = await this.parseJsonBody(request);\n await this.handleMigrate(podId, body, response);\n return;\n }\n\n throw new NotImplementedHttpError(`${method} ${url.pathname} not implemented`);\n } catch (error: unknown) {\n if (error instanceof BadRequestHttpError || \n error instanceof NotFoundHttpError ||\n error instanceof NotImplementedHttpError) {\n throw error;\n }\n this.logger.error(`Error handling ${method} ${url.pathname}: ${(error as Error).message}`);\n throw error;\n }\n }\n\n /**\n * List all pods with their node assignments.\n */\n private async handleListPods(response: HttpResponse): Promise<void> {\n const pods = await this.podLookupRepository.listAllPods();\n \n this.sendJson(response, 200, {\n pods: pods.map(pod => ({\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n })),\n });\n }\n\n /**\n * Get single pod info.\n */\n private async handleGetPod(podId: string, response: HttpResponse): Promise<void> {\n const pod = await this.podLookupRepository.findById(podId);\n if (!pod) {\n throw new NotFoundHttpError(`Pod ${podId} not found`);\n }\n\n this.sendJson(response, 200, {\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n });\n }\n\n /**\n * Migrate pod to target node.\n * This is instant - only updates nodeId. Binary files use cross-region fallback.\n */\n private async handleMigrate(\n podId: string,\n body: MigrateRequest,\n response: HttpResponse,\n ): Promise<void> {\n if (!body.targetNode) {\n throw new BadRequestHttpError('Missing required field: targetNode');\n }\n\n try {\n const result = await this.migrationService.migratePod(podId, body.targetNode);\n\n this.logger.info(`Pod migrated: pod=${podId}, from=${result.sourceNodeId}, to=${result.targetNodeId}`);\n\n this.sendJson(response, 200, {\n message: 'Migration completed',\n podId: result.podId,\n sourceNode: result.sourceNodeId,\n targetNode: result.targetNodeId,\n migratedAt: result.migratedAt.toISOString(),\n });\n } catch (error) {\n const message = (error as Error).message;\n \n if (message.includes('not found')) {\n throw new NotFoundHttpError(message);\n }\n if (message.includes('already on node')) {\n throw new BadRequestHttpError(message);\n }\n \n throw error;\n }\n }\n\n // ============ Utility methods ============\n\n private matchesPath(pathname: string): boolean {\n return pathname === this.basePath || pathname.startsWith(this.basePathWithSlash);\n }\n\n private getRelativePath(pathname: string): string {\n if (pathname === this.basePath) {\n return '';\n }\n if (pathname.startsWith(this.basePathWithSlash)) {\n return pathname.slice(this.basePathWithSlash.length);\n }\n return '';\n }\n\n private normalizeBasePath(basePath: string): string {\n let normalized = basePath;\n if (!normalized.startsWith('/')) {\n normalized = `/${normalized}`;\n }\n if (normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? 'localhost';\n return new URL(request.url ?? '/', `http://${hostHeader}`);\n }\n\n private async parseJsonBody(request: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8');\n resolve(body ? JSON.parse(body) : {});\n } catch (error) {\n reject(new BadRequestHttpError('Invalid JSON body'));\n }\n });\n request.on('error', reject);\n });\n }\n\n private sendJson(response: HttpResponse, status: number, data: object): void {\n const body = JSON.stringify(data);\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.setHeader('Content-Length', Buffer.byteLength(body));\n response.end(body);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return true; // Enabled by default\n }\n}\n"]}
1
+ {"version":3,"file":"PodMigrationHttpHandler.js","sourceRoot":"","sources":["../../../src/http/cluster/PodMigrationHttpHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n BadRequestHttpError,\n NotFoundHttpError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../../identity/drizzle/db';\nimport { PodLookupRepository } from '../../identity/drizzle/PodLookupRepository';\nimport { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { PodMigrationService } from '../../service/PodMigrationService';\n\ninterface PodMigrationHttpHandlerOptions {\n identityDbUrl: string;\n currentNodeId: string;\n basePath?: string;\n enabled?: boolean | string;\n}\n\ninterface MigrateRequest {\n targetNode: string;\n}\n\n/**\n * HTTP Handler for Pod migration operations.\n * \n * Migration is now instant - it only updates the Pod routing assignment.\n * Binary files are accessed via presigned URL redirect (302) from object storage.\n * \n * Endpoints:\n * - POST /.cluster/pods/{podId}/migrate - Migrate pod to target node (instant)\n * - GET /.cluster/pods/{podId} - Get pod info\n * - GET /.cluster/pods - List all pods with node info\n */\nexport class PodMigrationHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n \n private readonly podLookupRepository: PodLookupRepository;\n private readonly edgeNodeRepository: EdgeNodeRepository;\n private readonly migrationService: PodMigrationService;\n private readonly basePath: string;\n private readonly basePathWithSlash: string;\n private readonly enabled: boolean;\n\n public constructor(options: PodMigrationHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.podLookupRepository = new PodLookupRepository(db);\n this.edgeNodeRepository = new EdgeNodeRepository(db);\n this.migrationService = new PodMigrationService({\n identityDbUrl: options.identityDbUrl,\n currentNodeId: options.currentNodeId,\n });\n this.basePath = this.normalizeBasePath(options.basePath ?? '/.cluster/pods');\n this.basePathWithSlash = `${this.basePath}/`;\n this.enabled = this.normalizeBoolean(options.enabled);\n\n this.logger.info(`PodMigrationHttpHandler initialized: basePath=${this.basePath}, enabled=${this.enabled}`);\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Pod migration API disabled.');\n }\n\n const url = this.parseUrl(request);\n if (!this.matchesPath(url.pathname)) {\n throw new NotImplementedHttpError(`Path ${url.pathname} does not match ${this.basePath}`);\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n const method = (request.method ?? 'GET').toUpperCase();\n const relativePath = this.getRelativePath(url.pathname);\n\n try {\n // GET /.cluster/pods - List pods\n if (relativePath === '' && method === 'GET') {\n await this.handleListPods(response);\n return;\n }\n\n // Parse pod ID and action\n const match = relativePath.match(/^([^/]+)(?:\\/(.+))?$/);\n if (!match) {\n throw new BadRequestHttpError('Invalid path format. Expected: /pods/{podId} or /pods/{podId}/migrate');\n }\n\n const podId = decodeURIComponent(match[1]);\n const action = match[2];\n\n // GET /.cluster/pods/{podId} - Get pod info\n if (!action && method === 'GET') {\n await this.handleGetPod(podId, response);\n return;\n }\n\n // POST /.cluster/pods/{podId}/migrate - Migrate pod (instant)\n if (action === 'migrate' && method === 'POST') {\n const body = await this.parseJsonBody(request);\n await this.handleMigrate(podId, body, response);\n return;\n }\n\n throw new NotImplementedHttpError(`${method} ${url.pathname} not implemented`);\n } catch (error: unknown) {\n if (error instanceof BadRequestHttpError || \n error instanceof NotFoundHttpError ||\n error instanceof NotImplementedHttpError) {\n throw error;\n }\n this.logger.error(`Error handling ${method} ${url.pathname}: ${(error as Error).message}`);\n throw error;\n }\n }\n\n /**\n * List all pods with their node assignments.\n */\n private async handleListPods(response: HttpResponse): Promise<void> {\n const pods = await this.podLookupRepository.listAllPods();\n \n this.sendJson(response, 200, {\n pods: pods.map(pod => ({\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n })),\n });\n }\n\n /**\n * Get single pod info.\n */\n private async handleGetPod(podId: string, response: HttpResponse): Promise<void> {\n const pod = await this.podLookupRepository.findById(podId);\n if (!pod) {\n throw new NotFoundHttpError(`Pod ${podId} not found`);\n }\n\n this.sendJson(response, 200, {\n podId: pod.podId,\n baseUrl: pod.baseUrl,\n accountId: pod.accountId,\n nodeId: pod.nodeId,\n });\n }\n\n /**\n * Migrate pod to target node.\n * This is instant - only updates routing ownership. Binary files use cross-region fallback.\n */\n private async handleMigrate(\n podId: string,\n body: MigrateRequest,\n response: HttpResponse,\n ): Promise<void> {\n if (!body.targetNode) {\n throw new BadRequestHttpError('Missing required field: targetNode');\n }\n\n try {\n const result = await this.migrationService.migratePod(podId, body.targetNode);\n\n this.logger.info(`Pod migrated: pod=${podId}, from=${result.sourceNodeId}, to=${result.targetNodeId}`);\n\n this.sendJson(response, 200, {\n message: 'Migration completed',\n podId: result.podId,\n sourceNode: result.sourceNodeId,\n targetNode: result.targetNodeId,\n migratedAt: result.migratedAt.toISOString(),\n });\n } catch (error) {\n const message = (error as Error).message;\n \n if (message.includes('not found')) {\n throw new NotFoundHttpError(message);\n }\n if (message.includes('already on node')) {\n throw new BadRequestHttpError(message);\n }\n \n throw error;\n }\n }\n\n // ============ Utility methods ============\n\n private matchesPath(pathname: string): boolean {\n return pathname === this.basePath || pathname.startsWith(this.basePathWithSlash);\n }\n\n private getRelativePath(pathname: string): string {\n if (pathname === this.basePath) {\n return '';\n }\n if (pathname.startsWith(this.basePathWithSlash)) {\n return pathname.slice(this.basePathWithSlash.length);\n }\n return '';\n }\n\n private normalizeBasePath(basePath: string): string {\n let normalized = basePath;\n if (!normalized.startsWith('/')) {\n normalized = `/${normalized}`;\n }\n if (normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n }\n\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? 'localhost';\n return new URL(request.url ?? '/', `http://${hostHeader}`);\n }\n\n private async parseJsonBody(request: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8');\n resolve(body ? JSON.parse(body) : {});\n } catch (error) {\n reject(new BadRequestHttpError('Invalid JSON body'));\n }\n });\n request.on('error', reject);\n });\n }\n\n private sendJson(response: HttpResponse, status: number, data: object): void {\n const body = JSON.stringify(data);\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.setHeader('Content-Length', Buffer.byteLength(body));\n response.end(body);\n }\n\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return true; // Enabled by default\n }\n}\n"]}
@@ -50,11 +50,11 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
50
50
  }
51
51
  async handleGet(target, response) {
52
52
  if (target.type === 'account') {
53
- const quota = await this.quotaService.getAccountLimit(target.id);
53
+ const quota = await this.quotaService.getAccountQuota(target.id);
54
54
  this.writeJson(response, 200, {
55
55
  type: 'account',
56
56
  accountId: target.id,
57
- quotaLimit: quota ?? null,
57
+ quota,
58
58
  });
59
59
  return;
60
60
  }
@@ -62,13 +62,13 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
62
62
  if (!podInfo) {
63
63
  throw new community_server_2.BadRequestHttpError('Unknown pod identifier.');
64
64
  }
65
- const quota = await this.quotaService.getPodLimit(target.id);
65
+ const quota = await this.quotaService.getPodQuota(target.id);
66
66
  this.writeJson(response, 200, {
67
67
  type: 'pod',
68
68
  podId: target.id,
69
69
  accountId: podInfo.accountId,
70
70
  baseUrl: podInfo.baseUrl ?? null,
71
- quotaLimit: quota ?? null,
71
+ quota,
72
72
  });
73
73
  }
74
74
  async handlePut(target, request, response) {
@@ -85,23 +85,23 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
85
85
  }
86
86
  }
87
87
  if (target.type === 'account') {
88
- await this.quotaService.setAccountLimit(target.id, quota);
89
- const latest = await this.quotaService.getAccountLimit(target.id);
88
+ await this.quotaService.setAccountQuota(target.id, quota);
89
+ const latest = await this.quotaService.getAccountQuota(target.id);
90
90
  this.writeJson(response, 200, {
91
91
  status: 'updated',
92
92
  targetType: target.type,
93
93
  targetId: target.id,
94
- quotaLimit: latest ?? null,
94
+ quota: latest,
95
95
  });
96
96
  return;
97
97
  }
98
- await this.quotaService.setPodLimit(target.id, quota);
99
- const latest = await this.quotaService.getPodLimit(target.id);
98
+ await this.quotaService.setPodQuota(target.id, quota);
99
+ const latest = await this.quotaService.getPodQuota(target.id);
100
100
  this.writeJson(response, 200, {
101
101
  status: 'updated',
102
102
  targetType: target.type,
103
103
  targetId: target.id,
104
- quotaLimit: latest ?? null,
104
+ quota: latest,
105
105
  });
106
106
  }
107
107
  async handleDelete(target, response) {
@@ -112,10 +112,10 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
112
112
  }
113
113
  }
114
114
  if (target.type === 'account') {
115
- await this.quotaService.setAccountLimit(target.id, null);
115
+ await this.quotaService.clearAccountQuota(target.id);
116
116
  }
117
117
  else {
118
- await this.quotaService.setPodLimit(target.id, null);
118
+ await this.quotaService.clearPodQuota(target.id);
119
119
  }
120
120
  this.writeJson(response, 200, {
121
121
  status: 'cleared',
@@ -175,17 +175,23 @@ class QuotaAdminHttpHandler extends community_server_1.HttpHandler {
175
175
  }
176
176
  }
177
177
  extractQuota(body) {
178
- if (!Object.prototype.hasOwnProperty.call(body, 'quotaLimit')) {
179
- throw new community_server_2.BadRequestHttpError('Body must include quotaLimit.');
180
- }
181
- const quotaValue = body.quotaLimit;
182
- if (quotaValue === null) {
183
- return null;
178
+ const quota = {};
179
+ let hasQuotaField = false;
180
+ for (const field of ['storageLimitBytes', 'bandwidthLimitBps', 'computeLimitSeconds', 'tokenLimitMonthly']) {
181
+ if (!Object.prototype.hasOwnProperty.call(body, field)) {
182
+ continue;
183
+ }
184
+ const value = body[field];
185
+ if (value !== null && (typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {
186
+ throw new community_server_2.BadRequestHttpError(`${field} must be a non-negative number or null.`);
187
+ }
188
+ quota[field] = value;
189
+ hasQuotaField = true;
184
190
  }
185
- if (typeof quotaValue !== 'number' || !Number.isFinite(quotaValue) || quotaValue < 0) {
186
- throw new community_server_2.BadRequestHttpError('quotaLimit must be a non-negative number or null.');
191
+ if (!hasQuotaField) {
192
+ throw new community_server_2.BadRequestHttpError('Body must include at least one quota field.');
187
193
  }
188
- return quotaValue;
194
+ return quota;
189
195
  }
190
196
  writeOptions(response) {
191
197
  response.statusCode = 204;