@undefineds.co/xpod 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -3
- package/config/cli.json +9 -71
- package/config/cloud.json +34 -7
- package/config/local.json +6 -2
- package/config/resolver.json +11 -49
- package/config/runtime-open.json +22 -0
- package/config/xpod.base.json +32 -0
- package/config/xpod.cluster.json +2 -44
- package/config/xpod.json +5 -2
- package/dist/agents/AgentExecutorFactory.js +1 -1
- package/dist/agents/AgentExecutorFactory.js.map +1 -1
- package/dist/agents/AgentManager.js +1 -1
- package/dist/agents/AgentManager.js.map +1 -1
- package/dist/agents/config/agent-meta-schema.d.ts +7 -7
- package/dist/agents/config/agent-meta-schema.js +1 -1
- package/dist/agents/config/agent-meta-schema.js.map +1 -1
- package/dist/agents/config/resolve.js +1 -1
- package/dist/agents/config/resolve.js.map +1 -1
- package/dist/agents/schema/agent-config.d.ts +18 -18
- package/dist/agents/schema/agent-config.js +1 -1
- package/dist/agents/schema/agent-config.js.map +1 -1
- package/dist/agents/schema/tables.d.ts +8 -8
- package/dist/agents/schema/tables.js +1 -1
- package/dist/agents/schema/tables.js.map +1 -1
- package/dist/ai/schema/config.d.ts +7 -7
- package/dist/ai/schema/config.js +1 -1
- package/dist/ai/schema/config.js.map +1 -1
- package/dist/ai/schema/model.d.ts +13 -13
- package/dist/ai/schema/model.js +1 -1
- package/dist/ai/schema/model.js.map +1 -1
- package/dist/ai/schema/provider.d.ts +7 -7
- package/dist/ai/schema/provider.js +1 -1
- package/dist/ai/schema/provider.js.map +1 -1
- package/dist/ai/schema/vector-store.d.ts +17 -17
- package/dist/ai/schema/vector-store.js +1 -1
- package/dist/ai/schema/vector-store.js.map +1 -1
- package/dist/ai/service/CredentialReaderImpl.js +1 -1
- package/dist/ai/service/CredentialReaderImpl.js.map +1 -1
- package/dist/ai/service/DefaultAiConfigService.js.map +1 -1
- package/dist/api/ApiServer.d.ts +3 -1
- package/dist/api/ApiServer.js +14 -1
- package/dist/api/ApiServer.js.map +1 -1
- package/dist/api/auth/AuthContext.d.ts +12 -1
- package/dist/api/auth/AuthContext.js +18 -1
- package/dist/api/auth/AuthContext.js.map +1 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
- package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
- package/dist/api/auth/index.d.ts +1 -0
- package/dist/api/auth/index.js +1 -0
- package/dist/api/auth/index.js.map +1 -1
- package/dist/api/chatkit/ai-provider.d.ts +0 -10
- package/dist/api/chatkit/ai-provider.js +11 -120
- package/dist/api/chatkit/ai-provider.js.map +1 -1
- package/dist/api/chatkit/default-agent.js +11 -8
- package/dist/api/chatkit/default-agent.js.map +1 -1
- package/dist/api/chatkit/pod-store.d.ts +6 -0
- package/dist/api/chatkit/pod-store.js +103 -36
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/schema.d.ts +32 -26
- package/dist/api/chatkit/schema.js +16 -8
- package/dist/api/chatkit/schema.js.map +1 -1
- package/dist/api/container/business-token.d.ts +9 -0
- package/dist/api/container/business-token.js +32 -0
- package/dist/api/container/business-token.js.map +1 -0
- package/dist/api/container/cloud.js +36 -12
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/common.js +13 -5
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/index.js +94 -14
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +2 -1
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +81 -9
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +8 -6
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/AdminHandler.js +9 -9
- package/dist/api/handlers/AdminHandler.js.map +1 -1
- package/dist/api/handlers/ApiKeyHandler.js +0 -6
- package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
- package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
- package/dist/api/handlers/PodManagementHandler.js +11 -10
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
- package/dist/api/handlers/ProvisionHandler.js +161 -0
- package/dist/api/handlers/ProvisionHandler.js.map +1 -0
- package/dist/api/handlers/QuotaHandler.d.ts +7 -7
- package/dist/api/handlers/QuotaHandler.js +143 -73
- package/dist/api/handlers/QuotaHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainClientHandler.js +2 -2
- package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainHandler.js +13 -8
- package/dist/api/handlers/SubdomainHandler.js.map +1 -1
- package/dist/api/handlers/UsageHandler.d.ts +14 -0
- package/dist/api/handlers/UsageHandler.js +123 -0
- package/dist/api/handlers/UsageHandler.js.map +1 -0
- package/dist/api/handlers/index.d.ts +3 -1
- package/dist/api/handlers/index.js +3 -1
- package/dist/api/handlers/index.js.map +1 -1
- package/dist/api/main.js +18 -0
- package/dist/api/main.js.map +1 -1
- package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
- package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
- package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
- package/dist/api/runtime.d.ts +15 -0
- package/dist/api/runtime.js +125 -0
- package/dist/api/runtime.js.map +1 -0
- package/dist/api/service/VectorStoreService.js +1 -1
- package/dist/api/service/VectorStoreService.js.map +1 -1
- package/dist/api/service/VercelChatService.d.ts +16 -7
- package/dist/api/service/VercelChatService.js +98 -178
- package/dist/api/service/VercelChatService.js.map +1 -1
- package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
- package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
- package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
- package/dist/authorization/AuthModeSelector.d.ts +10 -0
- package/dist/authorization/AuthModeSelector.js +27 -0
- package/dist/authorization/AuthModeSelector.js.map +1 -0
- package/dist/authorization/AuthModeSelector.jsonld +81 -0
- package/dist/cli/commands/account.d.ts +6 -0
- package/dist/cli/commands/account.js +119 -0
- package/dist/cli/commands/account.js.map +1 -0
- package/dist/cli/commands/auth.js +20 -29
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/backup.d.ts +15 -0
- package/dist/cli/commands/backup.js +286 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.d.ts +34 -3
- package/dist/cli/commands/config.js +195 -258
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +94 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/pod.d.ts +6 -0
- package/dist/cli/commands/pod.js +124 -0
- package/dist/cli/commands/pod.js.map +1 -0
- package/dist/cli/commands/start.js +28 -5
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lib/credentials-store.d.ts +17 -0
- package/dist/cli/lib/credentials-store.js +73 -0
- package/dist/cli/lib/credentials-store.js.map +1 -0
- package/dist/cli/lib/css-account.d.ts +17 -0
- package/dist/cli/lib/css-account.js +56 -0
- package/dist/cli/lib/css-account.js.map +1 -1
- package/dist/cli/lib/pod-thread-store.d.ts +57 -0
- package/dist/cli/lib/pod-thread-store.js +310 -0
- package/dist/cli/lib/pod-thread-store.js.map +1 -0
- package/dist/cli/lib/solid-auth.d.ts +20 -0
- package/dist/cli/lib/solid-auth.js +70 -0
- package/dist/cli/lib/solid-auth.js.map +1 -0
- package/dist/components/components.jsonld +5 -8
- package/dist/components/context.jsonld +114 -244
- package/dist/credential/schema/tables.d.ts +14 -14
- package/dist/credential/schema/tables.js +1 -1
- package/dist/credential/schema/tables.js.map +1 -1
- package/dist/edge/EdgeNodeAgent.js +2 -2
- package/dist/edge/EdgeNodeAgent.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
- package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
- package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
- package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
- package/dist/edge/EdgeNodeModeDetector.js +9 -11
- package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
- package/dist/http/ClusterIngressRouter.js +3 -3
- package/dist/http/ClusterIngressRouter.js.map +1 -1
- package/dist/http/ClusterWebSocketConfigurator.js +2 -2
- package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
- package/dist/http/PodRoutingHttpHandler.js +2 -2
- package/dist/http/PodRoutingHttpHandler.js.map +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
- package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js +142 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
- package/dist/identity/drizzle/db.d.ts +9 -0
- package/dist/identity/drizzle/db.js +235 -3
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +5 -0
- package/dist/identity/drizzle/schema.pg.js +49 -20
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
- package/dist/identity/drizzle/schema.sqlite.js +48 -18
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +12 -14
- package/dist/index.js.map +1 -1
- package/dist/main.js +25 -8
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
- package/dist/provision/ProvisionCodeCodec.js +65 -0
- package/dist/provision/ProvisionCodeCodec.js.map +1 -0
- package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
- package/dist/provision/ProvisionPodCreator.d.ts +20 -0
- package/dist/provision/ProvisionPodCreator.js +84 -0
- package/dist/provision/ProvisionPodCreator.js.map +1 -0
- package/dist/provision/ProvisionPodCreator.jsonld +118 -0
- package/dist/quota/DrizzleQuotaService.d.ts +17 -3
- package/dist/quota/DrizzleQuotaService.js +108 -8
- package/dist/quota/DrizzleQuotaService.js.map +1 -1
- package/dist/quota/DrizzleQuotaService.jsonld +33 -22
- package/dist/quota/NoopQuotaService.d.ts +7 -1
- package/dist/quota/NoopQuotaService.js +12 -0
- package/dist/quota/NoopQuotaService.js.map +1 -1
- package/dist/quota/NoopQuotaService.jsonld +24 -0
- package/dist/quota/QuotaService.d.ts +17 -0
- package/dist/quota/QuotaService.js +5 -0
- package/dist/quota/QuotaService.js.map +1 -1
- package/dist/quota/QuotaService.jsonld +50 -0
- package/dist/runtime/Proxy.d.ts +22 -4
- package/dist/runtime/Proxy.js +154 -35
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/XpodRuntime.d.ts +49 -0
- package/dist/runtime/XpodRuntime.js +374 -0
- package/dist/runtime/XpodRuntime.js.map +1 -0
- package/dist/runtime/env-utils.d.ts +2 -0
- package/dist/runtime/env-utils.js +55 -0
- package/dist/runtime/env-utils.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +8 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/socket-fetch.d.ts +1 -0
- package/dist/runtime/socket-fetch.js +72 -0
- package/dist/runtime/socket-fetch.js.map +1 -0
- package/dist/runtime/socket-http.d.ts +1 -0
- package/dist/runtime/socket-http.js +142 -0
- package/dist/runtime/socket-http.js.map +1 -0
- package/dist/runtime/socket-utils.d.ts +2 -0
- package/dist/runtime/socket-utils.js +34 -0
- package/dist/runtime/socket-utils.js.map +1 -0
- package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
- package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
- package/dist/service/EdgeNodeSignalClient.js.map +1 -0
- package/dist/service/PodMigrationService.d.ts +1 -2
- package/dist/service/PodMigrationService.js +1 -2
- package/dist/service/PodMigrationService.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
- package/dist/storage/accessors/MinioDataAccessor.js +10 -0
- package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
- package/dist/storage/accessors/MixDataAccessor.js +12 -1
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
- package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
- package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
- package/dist/storage/quota/UsageRepository.d.ts +41 -8
- package/dist/storage/quota/UsageRepository.js +252 -50
- package/dist/storage/quota/UsageRepository.js.map +1 -1
- package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
- package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
- package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
- package/dist/storage/sparql/QueryOptimizer.js +13 -1
- package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
- package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
- package/dist/storage/sparql/QuintQuerySource.js +152 -1
- package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
- package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
- package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
- package/dist/subdomain/SubdomainClient.d.ts +3 -3
- package/dist/subdomain/SubdomainClient.js +1 -1
- package/dist/subdomain/SubdomainClient.js.map +1 -1
- package/dist/subdomain/SubdomainService.d.ts +15 -16
- package/dist/subdomain/SubdomainService.js +80 -54
- package/dist/subdomain/SubdomainService.js.map +1 -1
- package/dist/subdomain/SubdomainService.jsonld +22 -26
- package/dist/supervisor/Supervisor.d.ts +7 -2
- package/dist/supervisor/Supervisor.js +33 -1
- package/dist/supervisor/Supervisor.js.map +1 -1
- package/dist/task/DrizzleTaskQueue.d.ts +1 -1
- package/dist/task/DrizzleTaskQueue.js +1 -1
- package/dist/task/DrizzleTaskQueue.js.map +1 -1
- package/dist/task/schema.d.ts +10 -10
- package/dist/task/schema.js +1 -1
- package/dist/task/schema.js.map +1 -1
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.js +8 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/no-auth-xpod.d.ts +11 -0
- package/dist/test-utils/no-auth-xpod.js +25 -0
- package/dist/test-utils/no-auth-xpod.js.map +1 -0
- package/dist/test-utils/seed-pod.d.ts +5 -0
- package/dist/test-utils/seed-pod.js +61 -0
- package/dist/test-utils/seed-pod.js.map +1 -0
- package/package.json +38 -10
- package/templates/identity/account/create-pod.html.ejs +110 -0
- package/templates/main.html.ejs +10 -0
- package/dist/api/handlers/DevHandler.d.ts +0 -18
- package/dist/api/handlers/DevHandler.js +0 -276
- package/dist/api/handlers/DevHandler.js.map +0 -1
- package/dist/api/handlers/SignalHandler.d.ts +0 -13
- package/dist/api/handlers/SignalHandler.js +0 -122
- package/dist/api/handlers/SignalHandler.js.map +0 -1
- package/dist/gateway/Proxy.d.ts +0 -24
- package/dist/gateway/Proxy.js +0 -209
- package/dist/gateway/Proxy.js.map +0 -1
- package/dist/gateway/Supervisor.d.ts +0 -2
- package/dist/gateway/Supervisor.js +0 -7
- package/dist/gateway/Supervisor.js.map +0 -1
- package/dist/gateway/port-finder.d.ts +0 -4
- package/dist/gateway/port-finder.js +0 -15
- package/dist/gateway/port-finder.js.map +0 -1
- package/dist/gateway/types.d.ts +0 -1
- package/dist/gateway/types.js +0 -3
- package/dist/gateway/types.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
- package/dist/http/SignalInterceptHttpHandler.js +0 -47
- package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
- package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
- package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
- package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
- package/dist/quota/DefaultQuotaService.d.ts +0 -16
- package/dist/quota/DefaultQuotaService.js +0 -37
- package/dist/quota/DefaultQuotaService.js.map +0 -1
- package/dist/quota/DefaultQuotaService.jsonld +0 -85
- package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
- package/dist/service/PodMigrationService.jsonld +0 -76
- package/dist/storage/MigratableDataAccessor.d.ts +0 -63
- package/dist/storage/MigratableDataAccessor.js +0 -11
- package/dist/storage/MigratableDataAccessor.js.map +0 -1
- package/dist/storage/MigratableDataAccessor.jsonld +0 -60
- package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
- package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
- package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
- package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
- package/static/app/assets/index.css +0 -1
- package/static/app/assets/main.js +0 -11
|
@@ -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.publicIp) {\n // Direct mode: redirect client to connect directly\n const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';\n const directUrl = `wss://${nodeInfo.publicIp}${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 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"]}
|
|
@@ -117,9 +117,9 @@ class PodRoutingHttpHandler extends community_server_1.HttpHandler {
|
|
|
117
117
|
*/
|
|
118
118
|
resolveUpstream(nodeInfo) {
|
|
119
119
|
// Try public IP first (for edge nodes or external access)
|
|
120
|
-
if (nodeInfo.
|
|
120
|
+
if (nodeInfo.ipv4) {
|
|
121
121
|
const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';
|
|
122
|
-
return `https://${nodeInfo.
|
|
122
|
+
return `https://${nodeInfo.ipv4}${port}`;
|
|
123
123
|
}
|
|
124
124
|
// NOTE: Internal endpoint lookup is done via CenterNodeRegistrationService.getNodeInternalUrl()
|
|
125
125
|
// This method is kept simple - the handler should be injected with the registration service
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PodRoutingHttpHandler.js","sourceRoot":"","sources":["../../src/http/PodRoutingHttpHandler.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 NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { PodLookupRepository } from '../identity/drizzle/PodLookupRepository';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface PodRoutingHttpHandlerOptions {\n identityDbUrl: string;\n nodeId: string; // Current node ID\n enabled?: boolean | string;\n podLookupRepository?: PodLookupRepository;\n edgeNodeRepository?: EdgeNodeRepository;\n}\n\n/**\n * Pod Routing HTTP Handler - Routes requests to the correct node based on Pod location.\n * \n * For multi-node Center deployment:\n * 1. Extract Pod ID from request URL\n * 2. Look up Pod's nodeId from database\n * 3. If Pod is on current node, pass through to next handler\n * 4. If Pod is on another node, proxy the request\n */\nexport class PodRoutingHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly podLookupRepository: PodLookupRepository;\n private readonly edgeNodeRepository: EdgeNodeRepository;\n private readonly currentNodeId: string;\n private readonly enabled: boolean;\n\n public constructor(options: PodRoutingHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.podLookupRepository = options.podLookupRepository ?? new PodLookupRepository(db);\n this.edgeNodeRepository = options.edgeNodeRepository ?? new EdgeNodeRepository(db);\n this.currentNodeId = options.nodeId;\n this.enabled = this.normalizeBoolean(options.enabled);\n\n this.logger.info(`PodRoutingHttpHandler initialized: nodeId=${this.currentNodeId}, enabled=${this.enabled}`);\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Pod routing disabled.');\n }\n\n if (!this.currentNodeId) {\n throw new NotImplementedHttpError('No nodeId configured, pod routing disabled.');\n }\n\n const url = this.parseUrl(request);\n \n // Skip internal/system paths\n if (this.isSystemPath(url.pathname)) {\n throw new NotImplementedHttpError('System path, skip pod routing.');\n }\n\n // Look up Pod for this request\n const pod = await this.podLookupRepository.findByResourceIdentifier(url.href);\n if (!pod) {\n throw new NotImplementedHttpError('No Pod found for this request.');\n }\n\n // If Pod has no nodeId, it's on current node (legacy/default)\n if (!pod.nodeId) {\n throw new NotImplementedHttpError('Pod has no nodeId, handle locally.');\n }\n\n // If Pod is on current node, pass through\n if (pod.nodeId === this.currentNodeId) {\n throw new NotImplementedHttpError('Pod is on current node, handle locally.');\n }\n\n // Pod is on another node, we need to proxy\n this.logger.debug(`Pod ${pod.podId} is on node ${pod.nodeId}, need to proxy.`);\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n const pod = await this.podLookupRepository.findByResourceIdentifier(url.href);\n\n if (!pod || !pod.nodeId) {\n throw new InternalServerError('Pod lookup failed in handle phase.');\n }\n\n // Try to resolve upstream with internal endpoint support (for center-to-center)\n const upstream = await this.resolveUpstreamWithInternal(pod.nodeId);\n if (!upstream) {\n throw new InternalServerError(`Target node ${pod.nodeId} has no reachable endpoint.`);\n }\n\n await this.proxyRequest(request, response, upstream, url);\n }\n\n /**\n * Proxy request to target node.\n */\n private async proxyRequest(\n request: IncomingMessage,\n response: HttpResponse,\n upstream: string,\n originalUrl: URL,\n ): Promise<void> {\n const upstreamBase = new URL(upstream);\n const target = new URL(originalUrl.pathname + originalUrl.search, upstreamBase);\n target.hash = originalUrl.hash;\n\n const body = await this.readRequestBody(request);\n const headers = this.buildProxyHeaders(request, originalUrl, upstreamBase);\n\n this.logger.debug(`Proxying to ${target.toString()}`);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await fetch(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: ${String(error)}`);\n throw new InternalServerError('Failed to proxy request to target node.', { cause: error });\n }\n\n // Forward response\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Proxied-From', this.currentNodeId);\n\n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return; // Let Node.js handle chunking\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n\n const readable = Readable.from(upstreamResponse.body as unknown as AsyncIterable<Uint8Array>);\n readable.on('error', (error) => {\n this.logger.error(`Proxy stream error: ${String(error)}`);\n response.destroy(error);\n });\n readable.pipe(response);\n }\n\n /**\n * Resolve upstream endpoint from node info.\n * For center nodes, prefer internal endpoint for intra-cluster communication.\n */\n private resolveUpstream(nodeInfo: {\n publicIp?: string | null;\n publicPort?: number | null;\n nodeId: string;\n }): string | undefined {\n // Try public IP first (for edge nodes or external access)\n if (nodeInfo.publicIp) {\n const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';\n return `https://${nodeInfo.publicIp}${port}`;\n }\n\n // NOTE: Internal endpoint lookup is done via CenterNodeRegistrationService.getNodeInternalUrl()\n // This method is kept simple - the handler should be injected with the registration service\n // for full internal routing support.\n\n return undefined;\n }\n\n /**\n * Resolve upstream for center node using internal endpoint.\n */\n public async resolveUpstreamWithInternal(nodeId: string): Promise<string | undefined> {\n // First try to get center node internal endpoint\n const centerNodes = await this.edgeNodeRepository.listCenterNodes();\n const centerNode = centerNodes.find(n => n.nodeId === nodeId);\n \n if (centerNode && centerNode.internalIp && centerNode.internalPort) {\n return `http://${centerNode.internalIp}:${centerNode.internalPort}`;\n }\n\n // Fall back to public endpoint\n const nodeInfo = await this.edgeNodeRepository.getNodeConnectivityInfo(nodeId);\n if (!nodeInfo) {\n return undefined;\n }\n return this.resolveUpstream(nodeInfo);\n }\n\n /**\n * Build headers for proxy request.\n */\n private buildProxyHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n\n // Forward original headers except host\n for (const [name, value] of Object.entries(request.headers)) {\n if (value === undefined || 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\n // Set proper target host\n headers.set('host', upstream.host);\n\n // Add forwarded headers\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n\n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n\n // Add source node info\n headers.set('x-xpod-source-node', this.currentNodeId);\n\n // Add client IP if available\n const remoteAddress = (request.socket as { remoteAddress?: string })?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n\n return headers;\n }\n\n /**\n * Read request body for proxy forwarding.\n */\n private readRequestBody(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\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 /**\n * Check if path is a system/internal path that should skip routing.\n */\n private isSystemPath(pathname: string): boolean {\n const systemPaths = [\n '/idp/',\n '/.well-known/',\n '/-/',\n '/api/',\n ];\n return systemPaths.some(prefix => pathname.startsWith(prefix));\n }\n\n /**\n * Parse request URL.\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() ?? 'https' : 'https';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n /**\n * Normalize boolean values from string/boolean.\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":"PodRoutingHttpHandler.js","sourceRoot":"","sources":["../../src/http/PodRoutingHttpHandler.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 NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { PodLookupRepository } from '../identity/drizzle/PodLookupRepository';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface PodRoutingHttpHandlerOptions {\n identityDbUrl: string;\n nodeId: string; // Current node ID\n enabled?: boolean | string;\n podLookupRepository?: PodLookupRepository;\n edgeNodeRepository?: EdgeNodeRepository;\n}\n\n/**\n * Pod Routing HTTP Handler - Routes requests to the correct node based on Pod location.\n * \n * For multi-node Center deployment:\n * 1. Extract Pod ID from request URL\n * 2. Look up Pod's nodeId from database\n * 3. If Pod is on current node, pass through to next handler\n * 4. If Pod is on another node, proxy the request\n */\nexport class PodRoutingHttpHandler extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly podLookupRepository: PodLookupRepository;\n private readonly edgeNodeRepository: EdgeNodeRepository;\n private readonly currentNodeId: string;\n private readonly enabled: boolean;\n\n public constructor(options: PodRoutingHttpHandlerOptions) {\n super();\n const db = getIdentityDatabase(options.identityDbUrl);\n this.podLookupRepository = options.podLookupRepository ?? new PodLookupRepository(db);\n this.edgeNodeRepository = options.edgeNodeRepository ?? new EdgeNodeRepository(db);\n this.currentNodeId = options.nodeId;\n this.enabled = this.normalizeBoolean(options.enabled);\n\n this.logger.info(`PodRoutingHttpHandler initialized: nodeId=${this.currentNodeId}, enabled=${this.enabled}`);\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n if (!this.enabled) {\n throw new NotImplementedHttpError('Pod routing disabled.');\n }\n\n if (!this.currentNodeId) {\n throw new NotImplementedHttpError('No nodeId configured, pod routing disabled.');\n }\n\n const url = this.parseUrl(request);\n \n // Skip internal/system paths\n if (this.isSystemPath(url.pathname)) {\n throw new NotImplementedHttpError('System path, skip pod routing.');\n }\n\n // Look up Pod for this request\n const pod = await this.podLookupRepository.findByResourceIdentifier(url.href);\n if (!pod) {\n throw new NotImplementedHttpError('No Pod found for this request.');\n }\n\n // If Pod has no nodeId, it's on current node (legacy/default)\n if (!pod.nodeId) {\n throw new NotImplementedHttpError('Pod has no nodeId, handle locally.');\n }\n\n // If Pod is on current node, pass through\n if (pod.nodeId === this.currentNodeId) {\n throw new NotImplementedHttpError('Pod is on current node, handle locally.');\n }\n\n // Pod is on another node, we need to proxy\n this.logger.debug(`Pod ${pod.podId} is on node ${pod.nodeId}, need to proxy.`);\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const url = this.parseUrl(request);\n const pod = await this.podLookupRepository.findByResourceIdentifier(url.href);\n\n if (!pod || !pod.nodeId) {\n throw new InternalServerError('Pod lookup failed in handle phase.');\n }\n\n // Try to resolve upstream with internal endpoint support (for center-to-center)\n const upstream = await this.resolveUpstreamWithInternal(pod.nodeId);\n if (!upstream) {\n throw new InternalServerError(`Target node ${pod.nodeId} has no reachable endpoint.`);\n }\n\n await this.proxyRequest(request, response, upstream, url);\n }\n\n /**\n * Proxy request to target node.\n */\n private async proxyRequest(\n request: IncomingMessage,\n response: HttpResponse,\n upstream: string,\n originalUrl: URL,\n ): Promise<void> {\n const upstreamBase = new URL(upstream);\n const target = new URL(originalUrl.pathname + originalUrl.search, upstreamBase);\n target.hash = originalUrl.hash;\n\n const body = await this.readRequestBody(request);\n const headers = this.buildProxyHeaders(request, originalUrl, upstreamBase);\n\n this.logger.debug(`Proxying to ${target.toString()}`);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await fetch(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: ${String(error)}`);\n throw new InternalServerError('Failed to proxy request to target node.', { cause: error });\n }\n\n // Forward response\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Proxied-From', this.currentNodeId);\n\n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return; // Let Node.js handle chunking\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n\n const readable = Readable.from(upstreamResponse.body as unknown as AsyncIterable<Uint8Array>);\n readable.on('error', (error) => {\n this.logger.error(`Proxy stream error: ${String(error)}`);\n response.destroy(error);\n });\n readable.pipe(response);\n }\n\n /**\n * Resolve upstream endpoint from node info.\n * For center nodes, prefer internal endpoint for intra-cluster communication.\n */\n private resolveUpstream(nodeInfo: {\n ipv4?: string | null;\n publicPort?: number | null;\n nodeId: string;\n }): string | undefined {\n // Try public IP first (for edge nodes or external access)\n if (nodeInfo.ipv4) {\n const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';\n return `https://${nodeInfo.ipv4}${port}`;\n }\n\n // NOTE: Internal endpoint lookup is done via CenterNodeRegistrationService.getNodeInternalUrl()\n // This method is kept simple - the handler should be injected with the registration service\n // for full internal routing support.\n\n return undefined;\n }\n\n /**\n * Resolve upstream for center node using internal endpoint.\n */\n public async resolveUpstreamWithInternal(nodeId: string): Promise<string | undefined> {\n // First try to get center node internal endpoint\n const centerNodes = await this.edgeNodeRepository.listCenterNodes();\n const centerNode = centerNodes.find(n => n.nodeId === nodeId);\n \n if (centerNode && centerNode.internalIp && centerNode.internalPort) {\n return `http://${centerNode.internalIp}:${centerNode.internalPort}`;\n }\n\n // Fall back to public endpoint\n const nodeInfo = await this.edgeNodeRepository.getNodeConnectivityInfo(nodeId);\n if (!nodeInfo) {\n return undefined;\n }\n return this.resolveUpstream(nodeInfo);\n }\n\n /**\n * Build headers for proxy request.\n */\n private buildProxyHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n\n // Forward original headers except host\n for (const [name, value] of Object.entries(request.headers)) {\n if (value === undefined || 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\n // Set proper target host\n headers.set('host', upstream.host);\n\n // Add forwarded headers\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n\n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n\n // Add source node info\n headers.set('x-xpod-source-node', this.currentNodeId);\n\n // Add client IP if available\n const remoteAddress = (request.socket as { remoteAddress?: string })?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n\n return headers;\n }\n\n /**\n * Read request body for proxy forwarding.\n */\n private readRequestBody(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\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 /**\n * Check if path is a system/internal path that should skip routing.\n */\n private isSystemPath(pathname: string): boolean {\n const systemPaths = [\n '/idp/',\n '/.well-known/',\n '/-/',\n '/api/',\n ];\n return systemPaths.some(prefix => pathname.startsWith(prefix));\n }\n\n /**\n * Parse request URL.\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() ?? 'https' : 'https';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n /**\n * Normalize boolean values from string/boolean.\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"]}
|
|
@@ -10,7 +10,7 @@ interface PodMigrationHttpHandlerOptions {
|
|
|
10
10
|
* HTTP Handler for Pod migration operations.
|
|
11
11
|
*
|
|
12
12
|
* Migration is now instant - it only updates the nodeId in the database.
|
|
13
|
-
* Binary files are
|
|
13
|
+
* Binary files are accessed via presigned URL redirect (302) from object storage.
|
|
14
14
|
*
|
|
15
15
|
* Endpoints:
|
|
16
16
|
* - POST /.cluster/pods/{podId}/migrate - Migrate pod to target node (instant)
|
|
@@ -12,7 +12,7 @@ const PodMigrationService_1 = require("../../service/PodMigrationService");
|
|
|
12
12
|
* HTTP Handler for Pod migration operations.
|
|
13
13
|
*
|
|
14
14
|
* Migration is now instant - it only updates the nodeId in the database.
|
|
15
|
-
* Binary files are
|
|
15
|
+
* Binary files are accessed via presigned URL redirect (302) from object storage.
|
|
16
16
|
*
|
|
17
17
|
* Endpoints:
|
|
18
18
|
* - POST /.cluster/pods/{podId}/migrate - Migrate pod to target node (instant)
|
|
@@ -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 read via cross-region fallback (TieredMinioDataAccessor).\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 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"]}
|
|
@@ -2,7 +2,7 @@ import type { IdentityDatabase } from './db';
|
|
|
2
2
|
export interface EdgeNodeSummary {
|
|
3
3
|
nodeId: string;
|
|
4
4
|
displayName?: string;
|
|
5
|
-
nodeType: 'center' | 'edge';
|
|
5
|
+
nodeType: 'center' | 'edge' | 'sp';
|
|
6
6
|
podCount: number;
|
|
7
7
|
createdAt?: string;
|
|
8
8
|
updatedAt?: string;
|
|
@@ -18,7 +18,7 @@ export interface EdgeNodeSecret {
|
|
|
18
18
|
nodeId: string;
|
|
19
19
|
displayName?: string;
|
|
20
20
|
tokenHash: string;
|
|
21
|
-
nodeType: 'center' | 'edge';
|
|
21
|
+
nodeType: 'center' | 'edge' | 'sp';
|
|
22
22
|
metadata?: Record<string, unknown> | null;
|
|
23
23
|
}
|
|
24
24
|
export interface CenterNodeInfo {
|
|
@@ -29,6 +29,19 @@ export interface CenterNodeInfo {
|
|
|
29
29
|
connectivityStatus: 'unknown' | 'reachable' | 'unreachable';
|
|
30
30
|
lastSeen?: Date;
|
|
31
31
|
}
|
|
32
|
+
export interface CreateSpNodeResult {
|
|
33
|
+
nodeId: string;
|
|
34
|
+
nodeToken: string;
|
|
35
|
+
serviceToken: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
}
|
|
38
|
+
export interface SpNodeInfo {
|
|
39
|
+
nodeId: string;
|
|
40
|
+
displayName?: string;
|
|
41
|
+
publicUrl: string;
|
|
42
|
+
serviceTokenHash: string;
|
|
43
|
+
lastSeen?: Date;
|
|
44
|
+
}
|
|
32
45
|
export declare class EdgeNodeRepository {
|
|
33
46
|
private readonly db;
|
|
34
47
|
constructor(db: IdentityDatabase);
|
|
@@ -42,7 +55,7 @@ export declare class EdgeNodeRepository {
|
|
|
42
55
|
updateNodeHeartbeat(nodeId: string, metadata: Record<string, unknown> | null, timestamp: Date): Promise<void>;
|
|
43
56
|
updateNodeMode(nodeId: string, options: {
|
|
44
57
|
accessMode: 'direct' | 'proxy';
|
|
45
|
-
|
|
58
|
+
ipv4?: string;
|
|
46
59
|
publicPort?: number;
|
|
47
60
|
subdomain?: string;
|
|
48
61
|
connectivityStatus?: 'unknown' | 'reachable' | 'unreachable';
|
|
@@ -51,7 +64,7 @@ export declare class EdgeNodeRepository {
|
|
|
51
64
|
getNodeConnectivityInfo(nodeId: string): Promise<{
|
|
52
65
|
nodeId: string;
|
|
53
66
|
accessMode?: string;
|
|
54
|
-
|
|
67
|
+
ipv4?: string;
|
|
55
68
|
publicPort?: number;
|
|
56
69
|
subdomain?: string;
|
|
57
70
|
connectivityStatus?: string;
|
|
@@ -152,4 +165,24 @@ export declare class EdgeNodeRepository {
|
|
|
152
165
|
* Delete a node
|
|
153
166
|
*/
|
|
154
167
|
deleteNode(nodeId: string): Promise<boolean>;
|
|
168
|
+
/**
|
|
169
|
+
* Register or update an SP node (UPSERT by nodeId).
|
|
170
|
+
*
|
|
171
|
+
* SP 本地生成 deviceId 作为 nodeId,注册时带上来。
|
|
172
|
+
* 同一 nodeId 重复注册时更新 publicUrl、token 等,保留原记录。
|
|
173
|
+
* 不传 nodeId 则 Cloud 随机分配。
|
|
174
|
+
*/
|
|
175
|
+
registerSpNode(options: {
|
|
176
|
+
publicUrl: string;
|
|
177
|
+
displayName?: string;
|
|
178
|
+
ownerAccountId?: string;
|
|
179
|
+
/** SP 提供的设备 ID,作为 nodeId(不传则随机生成) */
|
|
180
|
+
nodeId?: string;
|
|
181
|
+
/** SP 提供的 serviceToken,不传则随机生成 */
|
|
182
|
+
serviceToken?: string;
|
|
183
|
+
}): Promise<CreateSpNodeResult>;
|
|
184
|
+
/**
|
|
185
|
+
* Get SP node info by nodeId.
|
|
186
|
+
*/
|
|
187
|
+
getSpNode(nodeId: string): Promise<SpNodeInfo | undefined>;
|
|
155
188
|
}
|