@undefineds.co/xpod 0.1.7 → 0.2.0-preview.2
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 +141 -2
- 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/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.js +19 -3
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/schema.d.ts +9 -3
- package/dist/api/chatkit/schema.js +14 -6
- 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 +12 -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 +104 -0
- package/dist/api/runtime.js.map +1 -0
- 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/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 +143 -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 +208 -1
- 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/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 +23 -5
- 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":"SubdomainClient.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainClient.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,iEAAqD;AA8ErD;;;;GAIG;AACH,MAAa,eAAe;IAO1B,YAAY,OAA+B;QAN1B,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAO3C,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,IAAY;QAClC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9E,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,OAAO,QAAgC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,OAId;QACC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,WAAW,CAAC;QAChD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACrC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC;SACH,CAAC,CAAC;QACH,OAAO,QAAuC,CAAC;IACjD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1D,OAAO,QAAyB,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,oBAAoB,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAClE,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,OAAO,QAA6D,CAAC;IACvE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QACnE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7D,OAAO,QAAiD,CAAC;IAC3D,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,IAAY;QAC5B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,IAAI,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC;QACzE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3D,OAAO,QAAiD,CAAC;IAC3D,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC;QACxE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3D,OAAO,QAAiD,CAAC;IAC3D,CAAC;IAED,qCAAqC;IAErC;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,OAMlB;QACC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,uBAAuB,CAAC;QAC5D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACrC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,cAAc,EAAE,OAAO,CAAC,cAAc;aACvC,CAAC;SACH,CAAC,CAAC;QACH,OAAO,QAAgC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,OAKnC;QACC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,gBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;QACpF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACrC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,EAAE,EAAE,OAAO,CAAC,SAAS;gBACrB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,cAAc,EAAE,OAAO,CAAC,cAAc;aACvC,CAAC;SACH,CAAC,CAAC;QACH,OAAO,QAA4B,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,gBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;QACpF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1D,OAAO,QAA0B,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,oBAAoB,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAClE,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,4CAA4C;IAEpC,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,OAGhC;QACC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAErE,IAAI,CAAC;YACH,yBAAyB;YACzB,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,wDAAa,QAAQ,GAAC,CAAC;YAC7D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;gBACtB,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE;aACrC,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE;gBACtC,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,eAAe,EAAE,UAAU,IAAI,CAAC,SAAS,EAAE;oBAC3C,WAAW,EAAE,IAAI,CAAC,MAAM;iBACzB;gBACD,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,UAAU,EAAE,KAAK;aAClB,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAEnC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,KAAK,GAAI,IAAY,EAAE,KAAK,IAAI,eAAe,CAAC;gBACtD,MAAM,IAAI,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;YACzD,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,oBAAoB,EAAE,CAAC;gBAC1C,MAAM,KAAK,CAAC;YACd,CAAC;YACD,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC1D,MAAM,IAAI,oBAAoB,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,IAAI,oBAAoB,CAC5B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EACxD,GAAG,CACJ,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;CACF;AApND,0CAoNC;AAED;;GAEG;AACH,MAAa,oBAAqB,SAAQ,KAAK;IAC7C,YACE,OAAe,EACC,MAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,WAAM,GAAN,MAAM,CAAQ;QAG9B,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AARD,oDAQC","sourcesContent":["/**\n * SubdomainClient - Local 模式子域名客户端\n * \n * Local 模式不持有 DNS/Tunnel 密钥,通过调用 Cloud API 来管理子域名。\n * 使用 Node Token 认证(与 Edge Node 心跳相同的认证方式)。\n */\n\nimport { getLoggerFor } from 'global-logger-factory';\n\nexport interface SubdomainClientOptions {\n /** Cloud API 端点 (如 https://center.example.com/v1/subdomain) */\n cloudApiEndpoint: string;\n \n /** 节点 ID */\n nodeId: string;\n \n /** 节点 Token */\n nodeToken: string;\n \n /** 请求超时 (ms) */\n timeoutMs?: number;\n}\n\nexport interface SubdomainCheckResult {\n subdomain: string;\n available: boolean;\n reason?: string;\n}\n\nexport interface SubdomainRegistrationResult {\n success: boolean;\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n publicIp?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n message?: string;\n}\n\nexport interface SubdomainInfo {\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n publicIp?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n ownerId?: string;\n}\n\nexport interface DdnsAllocationResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n createdAt: string;\n}\n\nexport interface DdnsUpdateResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n updatedAt: string;\n}\n\nexport interface DdnsRecordInfo {\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: string;\n status: string;\n ttl: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * 子域名客户端 (Local 模式)\n * \n * 通过 HTTP 调用 Cloud 的子域名 API\n */\nexport class SubdomainClient {\n private readonly logger = getLoggerFor(this);\n private readonly cloudApiEndpoint: string;\n private readonly nodeId: string;\n private readonly nodeToken: string;\n private readonly timeoutMs: number;\n\n constructor(options: SubdomainClientOptions) {\n this.cloudApiEndpoint = options.cloudApiEndpoint.replace(/\\/$/, '');\n this.nodeId = options.nodeId;\n this.nodeToken = options.nodeToken;\n this.timeoutMs = options.timeoutMs ?? 30000;\n }\n\n /**\n * 检查子域名可用性\n */\n async checkAvailability(name: string): Promise<SubdomainCheckResult> {\n const url = `${this.cloudApiEndpoint}/check?name=${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainCheckResult;\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n localPort: number;\n publicIp?: string;\n }): Promise<SubdomainRegistrationResult> {\n const url = `${this.cloudApiEndpoint}/register`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n localPort: options.localPort,\n publicIp: options.publicIp,\n nodeId: this.nodeId,\n }),\n });\n return response as SubdomainRegistrationResult;\n }\n\n /**\n * 获取子域名信息\n */\n async getInfo(name: string): Promise<SubdomainInfo | null> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * 列出所有子域名\n */\n async list(): Promise<{ registrations: SubdomainInfo[]; total: number }> {\n const url = this.cloudApiEndpoint;\n const response = await this.fetch(url, { method: 'GET' });\n return response as { registrations: SubdomainInfo[]; total: number };\n }\n\n /**\n * 释放子域名\n */\n async release(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'DELETE' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/start`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/stop`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n // ============ DDNS API ============\n\n /**\n * 分配 DDNS 子域名\n */\n async allocateDdns(options: {\n subdomain: string;\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n }): Promise<DdnsAllocationResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/allocate`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n nodeId: this.nodeId,\n ipAddress: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n }),\n });\n return response as DdnsAllocationResult;\n }\n\n /**\n * 更新 DDNS 记录\n */\n async updateDdns(subdomain: string, options: {\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n }): Promise<DdnsUpdateResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n ip: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n }),\n });\n return response as DdnsUpdateResult;\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getDdns(subdomain: string): Promise<DdnsRecordInfo | null> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as DdnsRecordInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n // ============ Private Methods ============\n\n private async fetch(url: string, options: {\n method: string;\n body?: string;\n }): Promise<unknown> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n // 使用 undici 的 fetch,禁用代理\n const { fetch: undiciFetch, Agent } = await import('undici');\n const agent = new Agent({\n connect: { timeout: this.timeoutMs },\n });\n\n const response = await undiciFetch(url, {\n method: options.method,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.nodeToken}`,\n 'X-Node-Id': this.nodeId,\n },\n body: options.body,\n signal: controller.signal,\n dispatcher: agent,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n const error = (data as any)?.error ?? 'Unknown error';\n throw new SubdomainClientError(error, response.status);\n }\n\n return data;\n } catch (error) {\n if (error instanceof SubdomainClientError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new SubdomainClientError('Request timeout', 408);\n }\n throw new SubdomainClientError(\n error instanceof Error ? error.message : 'Unknown error',\n 500,\n );\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\n/**\n * SubdomainClient 错误\n */\nexport class SubdomainClientError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n ) {\n super(message);\n this.name = 'SubdomainClientError';\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SubdomainClient.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainClient.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SubdomainClient - Local 模式子域名客户端\n * \n * Local 模式不持有 DNS/Tunnel 密钥,通过调用 Cloud API 来管理子域名。\n * 使用 Node Token 认证(与 Edge Node 心跳相同的认证方式)。\n */\n\nimport { getLoggerFor } from 'global-logger-factory';\n\nexport interface SubdomainClientOptions {\n /** Cloud API 端点 (如 https://center.example.com/v1/subdomain) */\n cloudApiEndpoint: string;\n \n /** 节点 ID */\n nodeId: string;\n \n /** 节点 Token */\n nodeToken: string;\n \n /** 请求超时 (ms) */\n timeoutMs?: number;\n}\n\nexport interface SubdomainCheckResult {\n subdomain: string;\n available: boolean;\n reason?: string;\n}\n\nexport interface SubdomainRegistrationResult {\n success: boolean;\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n ipv4?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n message?: string;\n}\n\nexport interface SubdomainInfo {\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n ipv4?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n ownerId?: string;\n}\n\nexport interface DdnsAllocationResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n createdAt: string;\n}\n\nexport interface DdnsUpdateResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n updatedAt: string;\n}\n\nexport interface DdnsRecordInfo {\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: string;\n status: string;\n ttl: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * 子域名客户端 (Local 模式)\n * \n * 通过 HTTP 调用 Cloud 的子域名 API\n */\nexport class SubdomainClient {\n private readonly logger = getLoggerFor(this);\n private readonly cloudApiEndpoint: string;\n private readonly nodeId: string;\n private readonly nodeToken: string;\n private readonly timeoutMs: number;\n\n constructor(options: SubdomainClientOptions) {\n this.cloudApiEndpoint = options.cloudApiEndpoint.replace(/\\/$/, '');\n this.nodeId = options.nodeId;\n this.nodeToken = options.nodeToken;\n this.timeoutMs = options.timeoutMs ?? 30000;\n }\n\n /**\n * 检查子域名可用性\n */\n async checkAvailability(name: string): Promise<SubdomainCheckResult> {\n const url = `${this.cloudApiEndpoint}/check?name=${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainCheckResult;\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n localPort: number;\n ipv4?: string;\n }): Promise<SubdomainRegistrationResult> {\n const url = `${this.cloudApiEndpoint}/register`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n localPort: options.localPort,\n ipv4: options.ipv4,\n nodeId: this.nodeId,\n }),\n });\n return response as SubdomainRegistrationResult;\n }\n\n /**\n * 获取子域名信息\n */\n async getInfo(name: string): Promise<SubdomainInfo | null> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * 列出所有子域名\n */\n async list(): Promise<{ registrations: SubdomainInfo[]; total: number }> {\n const url = this.cloudApiEndpoint;\n const response = await this.fetch(url, { method: 'GET' });\n return response as { registrations: SubdomainInfo[]; total: number };\n }\n\n /**\n * 释放子域名\n */\n async release(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'DELETE' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/start`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/stop`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n // ============ DDNS API ============\n\n /**\n * 分配 DDNS 子域名\n */\n async allocateDdns(options: {\n subdomain: string;\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n }): Promise<DdnsAllocationResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/allocate`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n nodeId: this.nodeId,\n ipAddress: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n }),\n });\n return response as DdnsAllocationResult;\n }\n\n /**\n * 更新 DDNS 记录\n */\n async updateDdns(subdomain: string, options: {\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n }): Promise<DdnsUpdateResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n ip: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n }),\n });\n return response as DdnsUpdateResult;\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getDdns(subdomain: string): Promise<DdnsRecordInfo | null> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as DdnsRecordInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n // ============ Private Methods ============\n\n private async fetch(url: string, options: {\n method: string;\n body?: string;\n }): Promise<unknown> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n // 使用 undici 的 fetch,禁用代理\n const { fetch: undiciFetch, Agent } = await import('undici');\n const agent = new Agent({\n connect: { timeout: this.timeoutMs },\n });\n\n const response = await undiciFetch(url, {\n method: options.method,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.nodeToken}`,\n 'X-Node-Id': this.nodeId,\n },\n body: options.body,\n signal: controller.signal,\n dispatcher: agent,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n const error = (data as any)?.error ?? 'Unknown error';\n throw new SubdomainClientError(error, response.status);\n }\n\n return data;\n } catch (error) {\n if (error instanceof SubdomainClientError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new SubdomainClientError('Request timeout', 408);\n }\n throw new SubdomainClientError(\n error instanceof Error ? error.message : 'Unknown error',\n 500,\n );\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\n/**\n * SubdomainClient 错误\n */\nexport class SubdomainClientError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n ) {\n super(message);\n this.name = 'SubdomainClientError';\n }\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TunnelProvider, TunnelConfig } from '../tunnel/TunnelProvider';
|
|
2
2
|
import type { DnsProvider } from '../dns/DnsProvider';
|
|
3
|
+
import type { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';
|
|
3
4
|
/**
|
|
4
5
|
* 子域名注册信息
|
|
5
6
|
*/
|
|
@@ -11,13 +12,15 @@ export interface SubdomainRegistration {
|
|
|
11
12
|
/** 访问模式 */
|
|
12
13
|
mode: 'direct' | 'tunnel';
|
|
13
14
|
/** 公网 IP (直连模式) */
|
|
14
|
-
|
|
15
|
+
ipv4?: string;
|
|
15
16
|
/** 隧道配置 (隧道模式) */
|
|
16
17
|
tunnelConfig?: TunnelConfig;
|
|
17
18
|
/** 注册时间 */
|
|
18
19
|
registeredAt: Date;
|
|
19
20
|
/** 所有者 ID */
|
|
20
21
|
ownerId?: string;
|
|
22
|
+
/** 绑定的节点 ID */
|
|
23
|
+
nodeId?: string;
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
23
26
|
* 连通性检测结果
|
|
@@ -26,7 +29,7 @@ export interface ConnectivityResult {
|
|
|
26
29
|
/** 是否可达 */
|
|
27
30
|
reachable: boolean;
|
|
28
31
|
/** 公网 IP */
|
|
29
|
-
|
|
32
|
+
ipv4?: string;
|
|
30
33
|
/** 延迟 (ms) */
|
|
31
34
|
latency?: number;
|
|
32
35
|
/** 错误信息 */
|
|
@@ -42,8 +45,8 @@ export interface SubdomainServiceOptions {
|
|
|
42
45
|
dnsProvider: DnsProvider;
|
|
43
46
|
/** Tunnel Provider */
|
|
44
47
|
tunnelProvider: TunnelProvider;
|
|
45
|
-
/**
|
|
46
|
-
|
|
48
|
+
/** Edge Node Repository (持久化) */
|
|
49
|
+
edgeNodeRepo: EdgeNodeRepository;
|
|
47
50
|
/** 保留的子域名列表 */
|
|
48
51
|
reservedSubdomains?: string[];
|
|
49
52
|
}
|
|
@@ -56,15 +59,15 @@ export interface SubdomainServiceOptions {
|
|
|
56
59
|
* 3. 直连/隧道模式选择
|
|
57
60
|
* 4. DNS 记录管理
|
|
58
61
|
* 5. 隧道创建
|
|
62
|
+
*
|
|
63
|
+
* 注册信息持久化到 EdgeNodeRepository(identity_edge_node.subdomain 字段)
|
|
59
64
|
*/
|
|
60
65
|
export declare class SubdomainService {
|
|
61
66
|
private readonly baseDomain;
|
|
62
67
|
private readonly dnsProvider;
|
|
63
68
|
private readonly tunnelProvider;
|
|
64
|
-
private readonly
|
|
69
|
+
private readonly edgeNodeRepo;
|
|
65
70
|
private readonly reservedSubdomains;
|
|
66
|
-
/** 已注册的子域名 (内存缓存,后续可改为数据库) */
|
|
67
|
-
private registrations;
|
|
68
71
|
constructor(options: SubdomainServiceOptions);
|
|
69
72
|
/**
|
|
70
73
|
* 检查子域名是否可用
|
|
@@ -78,8 +81,9 @@ export declare class SubdomainService {
|
|
|
78
81
|
*/
|
|
79
82
|
register(options: {
|
|
80
83
|
subdomain: string;
|
|
84
|
+
nodeId: string;
|
|
81
85
|
localPort: number;
|
|
82
|
-
|
|
86
|
+
ipv4?: string;
|
|
83
87
|
ownerId?: string;
|
|
84
88
|
}): Promise<SubdomainRegistration>;
|
|
85
89
|
/**
|
|
@@ -89,11 +93,11 @@ export declare class SubdomainService {
|
|
|
89
93
|
/**
|
|
90
94
|
* 获取注册信息
|
|
91
95
|
*/
|
|
92
|
-
getRegistration(subdomain: string): SubdomainRegistration | undefined
|
|
96
|
+
getRegistration(subdomain: string): Promise<SubdomainRegistration | undefined>;
|
|
93
97
|
/**
|
|
94
98
|
* 获取所有注册
|
|
95
99
|
*/
|
|
96
|
-
getAllRegistrations(): SubdomainRegistration[]
|
|
100
|
+
getAllRegistrations(): Promise<SubdomainRegistration[]>;
|
|
97
101
|
/**
|
|
98
102
|
* 启动隧道
|
|
99
103
|
*/
|
|
@@ -102,13 +106,8 @@ export declare class SubdomainService {
|
|
|
102
106
|
* 停止隧道
|
|
103
107
|
*/
|
|
104
108
|
stopTunnel(): Promise<void>;
|
|
105
|
-
|
|
106
|
-
* 校验子域名格式
|
|
107
|
-
*/
|
|
109
|
+
private nodeToRegistration;
|
|
108
110
|
private isValidSubdomain;
|
|
109
|
-
/**
|
|
110
|
-
* 连通性检测
|
|
111
|
-
*/
|
|
112
111
|
private checkConnectivity;
|
|
113
112
|
private isIpv6;
|
|
114
113
|
}
|
|
@@ -10,15 +10,15 @@ exports.SubdomainService = void 0;
|
|
|
10
10
|
* 3. 直连/隧道模式选择
|
|
11
11
|
* 4. DNS 记录管理
|
|
12
12
|
* 5. 隧道创建
|
|
13
|
+
*
|
|
14
|
+
* 注册信息持久化到 EdgeNodeRepository(identity_edge_node.subdomain 字段)
|
|
13
15
|
*/
|
|
14
16
|
class SubdomainService {
|
|
15
17
|
constructor(options) {
|
|
16
|
-
/** 已注册的子域名 (内存缓存,后续可改为数据库) */
|
|
17
|
-
this.registrations = new Map();
|
|
18
18
|
this.baseDomain = options.baseDomain;
|
|
19
19
|
this.dnsProvider = options.dnsProvider;
|
|
20
20
|
this.tunnelProvider = options.tunnelProvider;
|
|
21
|
-
this.
|
|
21
|
+
this.edgeNodeRepo = options.edgeNodeRepo;
|
|
22
22
|
this.reservedSubdomains = new Set(options.reservedSubdomains ?? [
|
|
23
23
|
'www', 'api', 'app', 'admin', 'mail', 'ftp', 'ssh',
|
|
24
24
|
'pods', 'center', 'edge', 'node', 'test', 'dev', 'staging',
|
|
@@ -28,22 +28,20 @@ class SubdomainService {
|
|
|
28
28
|
* 检查子域名是否可用
|
|
29
29
|
*/
|
|
30
30
|
async checkAvailability(subdomain) {
|
|
31
|
-
// 1. 格式校验
|
|
32
31
|
if (!this.isValidSubdomain(subdomain)) {
|
|
33
32
|
return {
|
|
34
33
|
available: false,
|
|
35
34
|
reason: 'Invalid subdomain format. Use 3-63 lowercase letters, numbers, or hyphens.',
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
|
-
// 2. 保留名检查
|
|
39
37
|
if (this.reservedSubdomains.has(subdomain.toLowerCase())) {
|
|
40
38
|
return {
|
|
41
39
|
available: false,
|
|
42
40
|
reason: 'This subdomain is reserved.',
|
|
43
41
|
};
|
|
44
42
|
}
|
|
45
|
-
|
|
46
|
-
if (
|
|
43
|
+
const existing = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());
|
|
44
|
+
if (existing) {
|
|
47
45
|
return {
|
|
48
46
|
available: false,
|
|
49
47
|
reason: 'This subdomain is already registered.',
|
|
@@ -55,31 +53,27 @@ class SubdomainService {
|
|
|
55
53
|
* 注册子域名
|
|
56
54
|
*/
|
|
57
55
|
async register(options) {
|
|
58
|
-
const { subdomain, localPort,
|
|
56
|
+
const { subdomain, nodeId, localPort, ipv4, ownerId } = options;
|
|
59
57
|
const normalizedSubdomain = subdomain.toLowerCase();
|
|
60
|
-
// 1. 检查可用性
|
|
61
58
|
const availability = await this.checkAvailability(normalizedSubdomain);
|
|
62
59
|
if (!availability.available) {
|
|
63
60
|
throw new Error(availability.reason);
|
|
64
61
|
}
|
|
65
62
|
const fullDomain = `${normalizedSubdomain}.${this.baseDomain}`;
|
|
66
|
-
//
|
|
63
|
+
// 连通性检测
|
|
67
64
|
let mode = 'tunnel';
|
|
68
65
|
let verifiedIp;
|
|
69
|
-
if (
|
|
70
|
-
const connectivity = await this.checkConnectivity(
|
|
71
|
-
// 如果可达,或者虽然不可达但我们是在 Local 模式且用户显式指定了 IP
|
|
72
|
-
// 这里可以放宽一点限制,因为有些网络环境可能单向不通但 DNS 应该先挂上去
|
|
66
|
+
if (ipv4) {
|
|
67
|
+
const connectivity = await this.checkConnectivity(ipv4, localPort);
|
|
73
68
|
if (connectivity.reachable) {
|
|
74
69
|
mode = 'direct';
|
|
75
|
-
verifiedIp =
|
|
70
|
+
verifiedIp = ipv4;
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
|
-
//
|
|
73
|
+
// DNS / 隧道
|
|
79
74
|
let tunnelConfig;
|
|
80
75
|
if (mode === 'direct') {
|
|
81
76
|
const type = this.isIpv6(verifiedIp) ? 'AAAA' : 'A';
|
|
82
|
-
// 直连模式:创建 A 或 AAAA 记录
|
|
83
77
|
await this.dnsProvider.upsertRecord({
|
|
84
78
|
subdomain: normalizedSubdomain,
|
|
85
79
|
domain: this.baseDomain,
|
|
@@ -89,76 +83,106 @@ class SubdomainService {
|
|
|
89
83
|
});
|
|
90
84
|
}
|
|
91
85
|
else {
|
|
92
|
-
// 隧道模式:创建隧道 + CNAME
|
|
93
86
|
tunnelConfig = await this.tunnelProvider.setup({
|
|
94
87
|
subdomain: normalizedSubdomain,
|
|
95
88
|
localPort,
|
|
96
89
|
});
|
|
97
90
|
}
|
|
98
|
-
//
|
|
99
|
-
|
|
91
|
+
// 持久化到 EdgeNodeRepository
|
|
92
|
+
await this.edgeNodeRepo.updateNodeMode(nodeId, {
|
|
93
|
+
accessMode: mode === 'direct' ? 'direct' : 'proxy',
|
|
94
|
+
ipv4: verifiedIp,
|
|
95
|
+
publicPort: localPort,
|
|
96
|
+
subdomain: normalizedSubdomain,
|
|
97
|
+
connectivityStatus: mode === 'direct' ? 'reachable' : 'unknown',
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
100
|
subdomain: normalizedSubdomain,
|
|
101
101
|
fullDomain,
|
|
102
102
|
mode,
|
|
103
|
-
|
|
103
|
+
ipv4: verifiedIp,
|
|
104
104
|
tunnelConfig,
|
|
105
105
|
registeredAt: new Date(),
|
|
106
106
|
ownerId,
|
|
107
|
+
nodeId,
|
|
107
108
|
};
|
|
108
|
-
this.registrations.set(normalizedSubdomain, registration);
|
|
109
|
-
return registration;
|
|
110
109
|
}
|
|
111
110
|
/**
|
|
112
111
|
* 释放子域名
|
|
113
112
|
*/
|
|
114
113
|
async release(subdomain) {
|
|
115
114
|
const normalizedSubdomain = subdomain.toLowerCase();
|
|
116
|
-
const
|
|
117
|
-
if (!
|
|
115
|
+
const node = await this.edgeNodeRepo.findNodeBySubdomain(normalizedSubdomain);
|
|
116
|
+
if (!node) {
|
|
118
117
|
throw new Error('Subdomain not found');
|
|
119
118
|
}
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// 2. 删除 DNS 记录 (尝试删除 A 和 CNAME 类型)
|
|
119
|
+
// 获取完整连通性信息以判断模式
|
|
120
|
+
const info = await this.edgeNodeRepo.getNodeConnectivityInfo(node.nodeId);
|
|
121
|
+
const accessMode = info?.accessMode;
|
|
122
|
+
// 删除 DNS 记录
|
|
125
123
|
try {
|
|
126
124
|
await this.dnsProvider.deleteRecord({
|
|
127
125
|
subdomain: normalizedSubdomain,
|
|
128
126
|
domain: this.baseDomain,
|
|
129
|
-
type:
|
|
127
|
+
type: accessMode === 'direct' ? 'A' : 'CNAME',
|
|
130
128
|
});
|
|
131
129
|
}
|
|
132
|
-
catch
|
|
133
|
-
|
|
130
|
+
catch {
|
|
131
|
+
// DNS 删除失败不阻塞释放
|
|
134
132
|
}
|
|
135
|
-
//
|
|
136
|
-
this.
|
|
133
|
+
// 清除 DB 中的 subdomain
|
|
134
|
+
await this.edgeNodeRepo.updateNodeMode(node.nodeId, {
|
|
135
|
+
accessMode: 'proxy',
|
|
136
|
+
subdomain: undefined,
|
|
137
|
+
connectivityStatus: 'unknown',
|
|
138
|
+
});
|
|
137
139
|
}
|
|
138
140
|
/**
|
|
139
141
|
* 获取注册信息
|
|
140
142
|
*/
|
|
141
|
-
getRegistration(subdomain) {
|
|
142
|
-
|
|
143
|
+
async getRegistration(subdomain) {
|
|
144
|
+
const node = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());
|
|
145
|
+
if (!node) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return this.nodeToRegistration(node);
|
|
143
149
|
}
|
|
144
150
|
/**
|
|
145
151
|
* 获取所有注册
|
|
146
152
|
*/
|
|
147
|
-
getAllRegistrations() {
|
|
148
|
-
|
|
153
|
+
async getAllRegistrations() {
|
|
154
|
+
const nodes = await this.edgeNodeRepo.listNodes();
|
|
155
|
+
const results = [];
|
|
156
|
+
for (const n of nodes) {
|
|
157
|
+
if (!n.metadata || !n.metadata.subdomain) {
|
|
158
|
+
// 需要查 connectivity info 获取 subdomain
|
|
159
|
+
const info = await this.edgeNodeRepo.getNodeConnectivityInfo(n.nodeId);
|
|
160
|
+
if (info?.subdomain) {
|
|
161
|
+
results.push({
|
|
162
|
+
subdomain: info.subdomain,
|
|
163
|
+
fullDomain: `${info.subdomain}.${this.baseDomain}`,
|
|
164
|
+
mode: info.accessMode === 'direct' ? 'direct' : 'tunnel',
|
|
165
|
+
ipv4: info.ipv4,
|
|
166
|
+
registeredAt: new Date(),
|
|
167
|
+
nodeId: n.nodeId,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return results;
|
|
149
173
|
}
|
|
150
174
|
/**
|
|
151
175
|
* 启动隧道
|
|
152
176
|
*/
|
|
153
177
|
async startTunnel(subdomain) {
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
178
|
+
const reg = await this.getRegistration(subdomain);
|
|
179
|
+
if (!reg) {
|
|
156
180
|
throw new Error('Subdomain not found');
|
|
157
181
|
}
|
|
158
|
-
if (
|
|
182
|
+
if (reg.mode !== 'tunnel' || !reg.tunnelConfig) {
|
|
159
183
|
throw new Error('Subdomain is not in tunnel mode');
|
|
160
184
|
}
|
|
161
|
-
await this.tunnelProvider.start(
|
|
185
|
+
await this.tunnelProvider.start(reg.tunnelConfig);
|
|
162
186
|
}
|
|
163
187
|
/**
|
|
164
188
|
* 停止隧道
|
|
@@ -167,34 +191,36 @@ class SubdomainService {
|
|
|
167
191
|
await this.tunnelProvider.stop();
|
|
168
192
|
}
|
|
169
193
|
// ============ 私有方法 ============
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
nodeToRegistration(node) {
|
|
195
|
+
const sub = node.subdomain;
|
|
196
|
+
return {
|
|
197
|
+
subdomain: sub,
|
|
198
|
+
fullDomain: `${sub}.${this.baseDomain}`,
|
|
199
|
+
mode: node.accessMode === 'direct' ? 'direct' : 'tunnel',
|
|
200
|
+
registeredAt: new Date(),
|
|
201
|
+
nodeId: node.nodeId,
|
|
202
|
+
ownerId: node.metadata?.ownerId,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
173
205
|
isValidSubdomain(subdomain) {
|
|
174
|
-
// 3-63 字符,小写字母、数字、连字符,不能以连字符开头或结尾
|
|
175
206
|
const regex = /^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$/;
|
|
176
207
|
return regex.test(subdomain) && subdomain.length >= 3 && subdomain.length <= 63;
|
|
177
208
|
}
|
|
178
|
-
/**
|
|
179
|
-
* 连通性检测
|
|
180
|
-
*/
|
|
181
209
|
async checkConnectivity(ip, port) {
|
|
182
210
|
try {
|
|
183
211
|
const start = Date.now();
|
|
184
212
|
const controller = new AbortController();
|
|
185
213
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
186
|
-
// IPv6 需要加中括号
|
|
187
214
|
const host = this.isIpv6(ip) ? `[${ip}]` : ip;
|
|
188
215
|
const response = await fetch(`http://${host}:${port}/.well-known/solid`, {
|
|
189
216
|
method: 'HEAD',
|
|
190
217
|
signal: controller.signal,
|
|
191
218
|
});
|
|
192
219
|
clearTimeout(timeout);
|
|
193
|
-
const latency = Date.now() - start;
|
|
194
220
|
return {
|
|
195
221
|
reachable: response.ok || response.status === 401,
|
|
196
|
-
|
|
197
|
-
latency,
|
|
222
|
+
ipv4: ip,
|
|
223
|
+
latency: Date.now() - start,
|
|
198
224
|
};
|
|
199
225
|
}
|
|
200
226
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubdomainService.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TunnelProvider, TunnelConfig } from '../tunnel/TunnelProvider';\nimport type { DnsProvider } from '../dns/DnsProvider';\n\n/**\n * 子域名注册信息\n */\nexport interface SubdomainRegistration {\n /** 子域名 (如 mynode) */\n subdomain: string;\n\n /** 完整域名 (如 mynode.pods.undefieds.co) */\n fullDomain: string;\n\n /** 访问模式 */\n mode: 'direct' | 'tunnel';\n\n /** 公网 IP (直连模式) */\n publicIp?: string;\n\n /** 隧道配置 (隧道模式) */\n tunnelConfig?: TunnelConfig;\n\n /** 注册时间 */\n registeredAt: Date;\n\n /** 所有者 ID */\n ownerId?: string;\n}\n\n/**\n * 连通性检测结果\n */\nexport interface ConnectivityResult {\n /** 是否可达 */\n reachable: boolean;\n\n /** 公网 IP */\n publicIp?: string;\n\n /** 延迟 (ms) */\n latency?: number;\n\n /** 错误信息 */\n error?: string;\n}\n\n/**\n * SubdomainService 配置\n */\nexport interface SubdomainServiceOptions {\n /** 基础域名 (如 pods.undefieds.co) */\n baseDomain: string;\n\n /** DNS Provider */\n dnsProvider: DnsProvider;\n\n /** Tunnel Provider */\n tunnelProvider: TunnelProvider;\n\n /** 连通性检测端点 (可选,用于回调检测) */\n connectivityCheckEndpoint?: string;\n\n /** 保留的子域名列表 */\n reservedSubdomains?: string[];\n}\n\n/**\n * 子域名管理服务\n * \n * 负责:\n * 1. 子域名可用性检查\n * 2. 连通性检测\n * 3. 直连/隧道模式选择\n * 4. DNS 记录管理\n * 5. 隧道创建\n */\nexport class SubdomainService {\n private readonly baseDomain: string;\n private readonly dnsProvider: DnsProvider;\n private readonly tunnelProvider: TunnelProvider;\n private readonly connectivityCheckEndpoint?: string;\n private readonly reservedSubdomains: Set<string>;\n\n /** 已注册的子域名 (内存缓存,后续可改为数据库) */\n private registrations: Map<string, SubdomainRegistration> = new Map();\n\n constructor(options: SubdomainServiceOptions) {\n this.baseDomain = options.baseDomain;\n this.dnsProvider = options.dnsProvider;\n this.tunnelProvider = options.tunnelProvider;\n this.connectivityCheckEndpoint = options.connectivityCheckEndpoint;\n this.reservedSubdomains = new Set(options.reservedSubdomains ?? [\n 'www', 'api', 'app', 'admin', 'mail', 'ftp', 'ssh',\n 'pods', 'center', 'edge', 'node', 'test', 'dev', 'staging',\n ]);\n }\n\n /**\n * 检查子域名是否可用\n */\n async checkAvailability(subdomain: string): Promise<{\n available: boolean;\n reason?: string;\n }> {\n // 1. 格式校验\n if (!this.isValidSubdomain(subdomain)) {\n return {\n available: false,\n reason: 'Invalid subdomain format. Use 3-63 lowercase letters, numbers, or hyphens.',\n };\n }\n\n // 2. 保留名检查\n if (this.reservedSubdomains.has(subdomain.toLowerCase())) {\n return {\n available: false,\n reason: 'This subdomain is reserved.',\n };\n }\n\n // 3. 已注册检查\n if (this.registrations.has(subdomain.toLowerCase())) {\n return {\n available: false,\n reason: 'This subdomain is already registered.',\n };\n }\n\n return { available: true };\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n localPort: number;\n publicIp?: string;\n ownerId?: string;\n }): Promise<SubdomainRegistration> {\n const { subdomain, localPort, publicIp, ownerId } = options;\n const normalizedSubdomain = subdomain.toLowerCase();\n\n // 1. 检查可用性\n const availability = await this.checkAvailability(normalizedSubdomain);\n if (!availability.available) {\n throw new Error(availability.reason);\n }\n\n const fullDomain = `${normalizedSubdomain}.${this.baseDomain}`;\n\n // 2. 连通性检测 (如果提供了公网 IP)\n let mode: 'direct' | 'tunnel' = 'tunnel';\n let verifiedIp: string | undefined;\n\n if (publicIp) {\n const connectivity = await this.checkConnectivity(publicIp, localPort);\n // 如果可达,或者虽然不可达但我们是在 Local 模式且用户显式指定了 IP\n // 这里可以放宽一点限制,因为有些网络环境可能单向不通但 DNS 应该先挂上去\n if (connectivity.reachable) {\n mode = 'direct';\n verifiedIp = publicIp;\n }\n }\n\n // 3. 根据模式设置 DNS 和隧道\n let tunnelConfig: TunnelConfig | undefined;\n\n if (mode === 'direct') {\n const type = this.isIpv6(verifiedIp!) ? 'AAAA' : 'A';\n // 直连模式:创建 A 或 AAAA 记录\n await this.dnsProvider.upsertRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type,\n value: verifiedIp!,\n ttl: 60,\n });\n } else {\n // 隧道模式:创建隧道 + CNAME\n tunnelConfig = await this.tunnelProvider.setup({\n subdomain: normalizedSubdomain,\n localPort,\n });\n }\n\n // 4. 保存注册信息\n const registration: SubdomainRegistration = {\n subdomain: normalizedSubdomain,\n fullDomain,\n mode,\n publicIp: verifiedIp,\n tunnelConfig,\n registeredAt: new Date(),\n ownerId,\n };\n\n this.registrations.set(normalizedSubdomain, registration);\n\n return registration;\n }\n\n /**\n * 释放子域名\n */\n async release(subdomain: string): Promise<void> {\n const normalizedSubdomain = subdomain.toLowerCase();\n const registration = this.registrations.get(normalizedSubdomain);\n\n if (!registration) {\n throw new Error('Subdomain not found');\n }\n\n // 1. 清理隧道\n if (registration.tunnelConfig) {\n await this.tunnelProvider.cleanup(registration.tunnelConfig);\n }\n\n // 2. 删除 DNS 记录 (尝试删除 A 和 CNAME 类型)\n try {\n await this.dnsProvider.deleteRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type: registration.mode === 'direct' ? 'A' : 'CNAME',\n });\n } catch (error) {\n console.warn(`Failed to delete DNS record for ${normalizedSubdomain}:`, error);\n }\n\n // 3. 移除注册信息\n this.registrations.delete(normalizedSubdomain);\n }\n\n /**\n * 获取注册信息\n */\n getRegistration(subdomain: string): SubdomainRegistration | undefined {\n return this.registrations.get(subdomain.toLowerCase());\n }\n\n /**\n * 获取所有注册\n */\n getAllRegistrations(): SubdomainRegistration[] {\n return Array.from(this.registrations.values());\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(subdomain: string): Promise<void> {\n const registration = this.registrations.get(subdomain.toLowerCase());\n\n if (!registration) {\n throw new Error('Subdomain not found');\n }\n\n if (registration.mode !== 'tunnel' || !registration.tunnelConfig) {\n throw new Error('Subdomain is not in tunnel mode');\n }\n\n await this.tunnelProvider.start(registration.tunnelConfig);\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(): Promise<void> {\n await this.tunnelProvider.stop();\n }\n\n // ============ 私有方法 ============\n\n /**\n * 校验子域名格式\n */\n private isValidSubdomain(subdomain: string): boolean {\n // 3-63 字符,小写字母、数字、连字符,不能以连字符开头或结尾\n const regex = /^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$/;\n return regex.test(subdomain) && subdomain.length >= 3 && subdomain.length <= 63;\n }\n\n /**\n * 连通性检测\n */\n private async checkConnectivity(\n ip: string,\n port: number\n ): Promise<ConnectivityResult> {\n try {\n const start = Date.now();\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n // IPv6 需要加中括号\n const host = this.isIpv6(ip) ? `[${ip}]` : ip;\n const response = await fetch(`http://${host}:${port}/.well-known/solid`, {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n const latency = Date.now() - start;\n\n return {\n reachable: response.ok || response.status === 401,\n publicIp: ip,\n latency,\n };\n } catch (error) {\n return {\n reachable: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n private isIpv6(ip: string): boolean {\n return ip.includes(':');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SubdomainService.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainService.ts"],"names":[],"mappings":";;;AAsEA;;;;;;;;;;;GAWG;AACH,MAAa,gBAAgB;IAO3B,YAAY,OAAgC;QAC1C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,kBAAkB,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,kBAAkB,IAAI;YAC9D,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;YAClD,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS;SAC3D,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,SAAiB;QAIvC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;YACtC,OAAO;gBACL,SAAS,EAAE,KAAK;gBAChB,MAAM,EAAE,4EAA4E;aACrF,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACzD,OAAO;gBACL,SAAS,EAAE,KAAK;gBAChB,MAAM,EAAE,6BAA6B;aACtC,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;QACtF,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO;gBACL,SAAS,EAAE,KAAK;gBAChB,MAAM,EAAE,uCAAuC;aAChD,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,OAMd;QACC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAChE,MAAM,mBAAmB,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QAEpD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;QACvE,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,mBAAmB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QAE/D,QAAQ;QACR,IAAI,IAAI,GAAwB,QAAQ,CAAC;QACzC,IAAI,UAA8B,CAAC;QAEnC,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACnE,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;gBAC3B,IAAI,GAAG,QAAQ,CAAC;gBAChB,UAAU,GAAG,IAAI,CAAC;YACpB,CAAC;QACH,CAAC;QAED,WAAW;QACX,IAAI,YAAsC,CAAC;QAE3C,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,UAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;YACrD,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;gBAClC,SAAS,EAAE,mBAAmB;gBAC9B,MAAM,EAAE,IAAI,CAAC,UAAU;gBACvB,IAAI;gBACJ,KAAK,EAAE,UAAW;gBAClB,GAAG,EAAE,EAAE;aACR,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,YAAY,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;gBAC7C,SAAS,EAAE,mBAAmB;gBAC9B,SAAS;aACV,CAAC,CAAC;QACL,CAAC;QAED,0BAA0B;QAC1B,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,MAAM,EAAE;YAC7C,UAAU,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO;YAClD,IAAI,EAAE,UAAU;YAChB,UAAU,EAAE,SAAS;YACrB,SAAS,EAAE,mBAAmB;YAC9B,kBAAkB,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;SAChE,CAAC,CAAC;QAEH,OAAO;YACL,SAAS,EAAE,mBAAmB;YAC9B,UAAU;YACV,IAAI;YACJ,IAAI,EAAE,UAAU;YAChB,YAAY;YACZ,YAAY,EAAE,IAAI,IAAI,EAAE;YACxB,OAAO;YACP,MAAM;SACP,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,MAAM,mBAAmB,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,mBAAmB,CAAC,CAAC;QAE9E,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACzC,CAAC;QAED,iBAAiB;QACjB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1E,MAAM,UAAU,GAAG,IAAI,EAAE,UAAU,CAAC;QAEpC,YAAY;QACZ,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;gBAClC,SAAS,EAAE,mBAAmB;gBAC9B,MAAM,EAAE,IAAI,CAAC,UAAU;gBACvB,IAAI,EAAE,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO;aAC9C,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB;QAClB,CAAC;QAED,qBAAqB;QACrB,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE;YAClD,UAAU,EAAE,OAAO;YACnB,SAAS,EAAE,SAAS;YACpB,kBAAkB,EAAE,SAAS;SAC9B,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CAAC,SAAiB;QACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,mBAAmB;QACvB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC;QAClD,MAAM,OAAO,GAA4B,EAAE,CAAC;QAC5C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAE,CAAC,CAAC,QAAoC,CAAC,SAAS,EAAE,CAAC;gBACtE,qCAAqC;gBACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBACvE,IAAI,IAAI,EAAE,SAAS,EAAE,CAAC;oBACpB,OAAO,CAAC,IAAI,CAAC;wBACX,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE;wBAClD,IAAI,EAAE,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;wBACxD,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,YAAY,EAAE,IAAI,IAAI,EAAE;wBACxB,MAAM,EAAE,CAAC,CAAC,MAAM;qBACjB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB;QACjC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACzC,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;QACD,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACnC,CAAC;IAED,iCAAiC;IAEzB,kBAAkB,CAAC,IAK1B;QACC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAU,CAAC;QAC5B,OAAO;YACL,SAAS,EAAE,GAAG;YACd,UAAU,EAAE,GAAG,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE;YACvC,IAAI,EAAE,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;YACxD,YAAY,EAAE,IAAI,IAAI,EAAE;YACxB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAG,IAAI,CAAC,QAA2C,EAAE,OAA6B;SAC1F,CAAC;IACJ,CAAC;IAEO,gBAAgB,CAAC,SAAiB;QACxC,MAAM,KAAK,GAAG,sCAAsC,CAAC;QACrD,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,IAAI,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC;IAClF,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAC7B,EAAU,EACV,IAAY;QAEZ,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;YAE3D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,UAAU,IAAI,IAAI,IAAI,oBAAoB,EAAE;gBACvE,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YAEH,YAAY,CAAC,OAAO,CAAC,CAAC;YAEtB,OAAO;gBACL,SAAS,EAAE,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;gBACjD,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;aAC5B,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,SAAS,EAAE,KAAK;gBAChB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAChE,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,EAAU;QACvB,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;CACF;AA9QD,4CA8QC","sourcesContent":["import type { TunnelProvider, TunnelConfig } from '../tunnel/TunnelProvider';\nimport type { DnsProvider } from '../dns/DnsProvider';\nimport type { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\n/**\n * 子域名注册信息\n */\nexport interface SubdomainRegistration {\n /** 子域名 (如 mynode) */\n subdomain: string;\n\n /** 完整域名 (如 mynode.pods.undefieds.co) */\n fullDomain: string;\n\n /** 访问模式 */\n mode: 'direct' | 'tunnel';\n\n /** 公网 IP (直连模式) */\n ipv4?: string;\n\n /** 隧道配置 (隧道模式) */\n tunnelConfig?: TunnelConfig;\n\n /** 注册时间 */\n registeredAt: Date;\n\n /** 所有者 ID */\n ownerId?: string;\n\n /** 绑定的节点 ID */\n nodeId?: string;\n}\n\n/**\n * 连通性检测结果\n */\nexport interface ConnectivityResult {\n /** 是否可达 */\n reachable: boolean;\n\n /** 公网 IP */\n ipv4?: string;\n\n /** 延迟 (ms) */\n latency?: number;\n\n /** 错误信息 */\n error?: string;\n}\n\n/**\n * SubdomainService 配置\n */\nexport interface SubdomainServiceOptions {\n /** 基础域名 (如 pods.undefieds.co) */\n baseDomain: string;\n\n /** DNS Provider */\n dnsProvider: DnsProvider;\n\n /** Tunnel Provider */\n tunnelProvider: TunnelProvider;\n\n /** Edge Node Repository (持久化) */\n edgeNodeRepo: EdgeNodeRepository;\n\n /** 保留的子域名列表 */\n reservedSubdomains?: string[];\n}\n\n/**\n * 子域名管理服务\n *\n * 负责:\n * 1. 子域名可用性检查\n * 2. 连通性检测\n * 3. 直连/隧道模式选择\n * 4. DNS 记录管理\n * 5. 隧道创建\n *\n * 注册信息持久化到 EdgeNodeRepository(identity_edge_node.subdomain 字段)\n */\nexport class SubdomainService {\n private readonly baseDomain: string;\n private readonly dnsProvider: DnsProvider;\n private readonly tunnelProvider: TunnelProvider;\n private readonly edgeNodeRepo: EdgeNodeRepository;\n private readonly reservedSubdomains: Set<string>;\n\n constructor(options: SubdomainServiceOptions) {\n this.baseDomain = options.baseDomain;\n this.dnsProvider = options.dnsProvider;\n this.tunnelProvider = options.tunnelProvider;\n this.edgeNodeRepo = options.edgeNodeRepo;\n this.reservedSubdomains = new Set(options.reservedSubdomains ?? [\n 'www', 'api', 'app', 'admin', 'mail', 'ftp', 'ssh',\n 'pods', 'center', 'edge', 'node', 'test', 'dev', 'staging',\n ]);\n }\n\n /**\n * 检查子域名是否可用\n */\n async checkAvailability(subdomain: string): Promise<{\n available: boolean;\n reason?: string;\n }> {\n if (!this.isValidSubdomain(subdomain)) {\n return {\n available: false,\n reason: 'Invalid subdomain format. Use 3-63 lowercase letters, numbers, or hyphens.',\n };\n }\n\n if (this.reservedSubdomains.has(subdomain.toLowerCase())) {\n return {\n available: false,\n reason: 'This subdomain is reserved.',\n };\n }\n\n const existing = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());\n if (existing) {\n return {\n available: false,\n reason: 'This subdomain is already registered.',\n };\n }\n\n return { available: true };\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n nodeId: string;\n localPort: number;\n ipv4?: string;\n ownerId?: string;\n }): Promise<SubdomainRegistration> {\n const { subdomain, nodeId, localPort, ipv4, ownerId } = options;\n const normalizedSubdomain = subdomain.toLowerCase();\n\n const availability = await this.checkAvailability(normalizedSubdomain);\n if (!availability.available) {\n throw new Error(availability.reason);\n }\n\n const fullDomain = `${normalizedSubdomain}.${this.baseDomain}`;\n\n // 连通性检测\n let mode: 'direct' | 'tunnel' = 'tunnel';\n let verifiedIp: string | undefined;\n\n if (ipv4) {\n const connectivity = await this.checkConnectivity(ipv4, localPort);\n if (connectivity.reachable) {\n mode = 'direct';\n verifiedIp = ipv4;\n }\n }\n\n // DNS / 隧道\n let tunnelConfig: TunnelConfig | undefined;\n\n if (mode === 'direct') {\n const type = this.isIpv6(verifiedIp!) ? 'AAAA' : 'A';\n await this.dnsProvider.upsertRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type,\n value: verifiedIp!,\n ttl: 60,\n });\n } else {\n tunnelConfig = await this.tunnelProvider.setup({\n subdomain: normalizedSubdomain,\n localPort,\n });\n }\n\n // 持久化到 EdgeNodeRepository\n await this.edgeNodeRepo.updateNodeMode(nodeId, {\n accessMode: mode === 'direct' ? 'direct' : 'proxy',\n ipv4: verifiedIp,\n publicPort: localPort,\n subdomain: normalizedSubdomain,\n connectivityStatus: mode === 'direct' ? 'reachable' : 'unknown',\n });\n\n return {\n subdomain: normalizedSubdomain,\n fullDomain,\n mode,\n ipv4: verifiedIp,\n tunnelConfig,\n registeredAt: new Date(),\n ownerId,\n nodeId,\n };\n }\n\n /**\n * 释放子域名\n */\n async release(subdomain: string): Promise<void> {\n const normalizedSubdomain = subdomain.toLowerCase();\n const node = await this.edgeNodeRepo.findNodeBySubdomain(normalizedSubdomain);\n\n if (!node) {\n throw new Error('Subdomain not found');\n }\n\n // 获取完整连通性信息以判断模式\n const info = await this.edgeNodeRepo.getNodeConnectivityInfo(node.nodeId);\n const accessMode = info?.accessMode;\n\n // 删除 DNS 记录\n try {\n await this.dnsProvider.deleteRecord({\n subdomain: normalizedSubdomain,\n domain: this.baseDomain,\n type: accessMode === 'direct' ? 'A' : 'CNAME',\n });\n } catch {\n // DNS 删除失败不阻塞释放\n }\n\n // 清除 DB 中的 subdomain\n await this.edgeNodeRepo.updateNodeMode(node.nodeId, {\n accessMode: 'proxy',\n subdomain: undefined,\n connectivityStatus: 'unknown',\n });\n }\n\n /**\n * 获取注册信息\n */\n async getRegistration(subdomain: string): Promise<SubdomainRegistration | undefined> {\n const node = await this.edgeNodeRepo.findNodeBySubdomain(subdomain.toLowerCase());\n if (!node) {\n return undefined;\n }\n return this.nodeToRegistration(node);\n }\n\n /**\n * 获取所有注册\n */\n async getAllRegistrations(): Promise<SubdomainRegistration[]> {\n const nodes = await this.edgeNodeRepo.listNodes();\n const results: SubdomainRegistration[] = [];\n for (const n of nodes) {\n if (!n.metadata || !(n.metadata as Record<string, unknown>).subdomain) {\n // 需要查 connectivity info 获取 subdomain\n const info = await this.edgeNodeRepo.getNodeConnectivityInfo(n.nodeId);\n if (info?.subdomain) {\n results.push({\n subdomain: info.subdomain,\n fullDomain: `${info.subdomain}.${this.baseDomain}`,\n mode: info.accessMode === 'direct' ? 'direct' : 'tunnel',\n ipv4: info.ipv4,\n registeredAt: new Date(),\n nodeId: n.nodeId,\n });\n }\n }\n }\n return results;\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(subdomain: string): Promise<void> {\n const reg = await this.getRegistration(subdomain);\n if (!reg) {\n throw new Error('Subdomain not found');\n }\n if (reg.mode !== 'tunnel' || !reg.tunnelConfig) {\n throw new Error('Subdomain is not in tunnel mode');\n }\n await this.tunnelProvider.start(reg.tunnelConfig);\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(): Promise<void> {\n await this.tunnelProvider.stop();\n }\n\n // ============ 私有方法 ============\n\n private nodeToRegistration(node: {\n nodeId: string;\n accessMode?: string;\n metadata?: Record<string, unknown> | null;\n subdomain?: string;\n }): SubdomainRegistration {\n const sub = node.subdomain!;\n return {\n subdomain: sub,\n fullDomain: `${sub}.${this.baseDomain}`,\n mode: node.accessMode === 'direct' ? 'direct' : 'tunnel',\n registeredAt: new Date(),\n nodeId: node.nodeId,\n ownerId: (node.metadata as Record<string, unknown> | null)?.ownerId as string | undefined,\n };\n }\n\n private isValidSubdomain(subdomain: string): boolean {\n const regex = /^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$/;\n return regex.test(subdomain) && subdomain.length >= 3 && subdomain.length <= 63;\n }\n\n private async checkConnectivity(\n ip: string,\n port: number,\n ): Promise<ConnectivityResult> {\n try {\n const start = Date.now();\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n const host = this.isIpv6(ip) ? `[${ip}]` : ip;\n const response = await fetch(`http://${host}:${port}/.well-known/solid`, {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n return {\n reachable: response.ok || response.status === 401,\n ipv4: ip,\n latency: Date.now() - start,\n };\n } catch (error) {\n return {\n reachable: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n private isIpv6(ip: string): boolean {\n return ip.includes(':');\n }\n}\n"]}
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"memberFieldName": "mode"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#
|
|
28
|
-
"memberFieldName": "
|
|
27
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainRegistration__member_ipv4",
|
|
28
|
+
"memberFieldName": "ipv4"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainRegistration__member_tunnelConfig",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
{
|
|
39
39
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainRegistration__member_ownerId",
|
|
40
40
|
"memberFieldName": "ownerId"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainRegistration__member_nodeId",
|
|
44
|
+
"memberFieldName": "nodeId"
|
|
41
45
|
}
|
|
42
46
|
],
|
|
43
47
|
"constructorArguments": []
|
|
@@ -54,8 +58,8 @@
|
|
|
54
58
|
"memberFieldName": "reachable"
|
|
55
59
|
},
|
|
56
60
|
{
|
|
57
|
-
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#
|
|
58
|
-
"memberFieldName": "
|
|
61
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#ConnectivityResult__member_ipv4",
|
|
62
|
+
"memberFieldName": "ipv4"
|
|
59
63
|
},
|
|
60
64
|
{
|
|
61
65
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#ConnectivityResult__member_latency",
|
|
@@ -88,8 +92,8 @@
|
|
|
88
92
|
"memberFieldName": "tunnelProvider"
|
|
89
93
|
},
|
|
90
94
|
{
|
|
91
|
-
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#
|
|
92
|
-
"memberFieldName": "
|
|
95
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainServiceOptions__member_edgeNodeRepo",
|
|
96
|
+
"memberFieldName": "edgeNodeRepo"
|
|
93
97
|
},
|
|
94
98
|
{
|
|
95
99
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainServiceOptions__member_reservedSubdomains",
|
|
@@ -102,7 +106,7 @@
|
|
|
102
106
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService",
|
|
103
107
|
"@type": "Class",
|
|
104
108
|
"requireElement": "SubdomainService",
|
|
105
|
-
"comment": "子域名管理服务 负责: 1. 子域名可用性检查 2. 连通性检测 3. 直连/隧道模式选择 4. DNS 记录管理 5. 隧道创建",
|
|
109
|
+
"comment": "子域名管理服务 负责: 1. 子域名可用性检查 2. 连通性检测 3. 直连/隧道模式选择 4. DNS 记录管理 5. 隧道创建 注册信息持久化到 EdgeNodeRepository(identity_edge_node.subdomain 字段)",
|
|
106
110
|
"parameters": [
|
|
107
111
|
{
|
|
108
112
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService_options_baseDomain",
|
|
@@ -120,17 +124,9 @@
|
|
|
120
124
|
"comment": "Tunnel Provider"
|
|
121
125
|
},
|
|
122
126
|
{
|
|
123
|
-
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#
|
|
124
|
-
"range":
|
|
125
|
-
|
|
126
|
-
"parameterRangeElements": [
|
|
127
|
-
"xsd:string",
|
|
128
|
-
{
|
|
129
|
-
"@type": "ParameterRangeUndefined"
|
|
130
|
-
}
|
|
131
|
-
]
|
|
132
|
-
},
|
|
133
|
-
"comment": "连通性检测端点 (可选,用于回调检测)"
|
|
127
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService_options_edgeNodeRepo",
|
|
128
|
+
"range": "undefineds:dist/identity/drizzle/EdgeNodeRepository.jsonld#EdgeNodeRepository",
|
|
129
|
+
"comment": "Edge Node Repository (持久化)"
|
|
134
130
|
},
|
|
135
131
|
{
|
|
136
132
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService_options_reservedSubdomains",
|
|
@@ -163,17 +159,13 @@
|
|
|
163
159
|
"memberFieldName": "tunnelProvider"
|
|
164
160
|
},
|
|
165
161
|
{
|
|
166
|
-
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#
|
|
167
|
-
"memberFieldName": "
|
|
162
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService__member_edgeNodeRepo",
|
|
163
|
+
"memberFieldName": "edgeNodeRepo"
|
|
168
164
|
},
|
|
169
165
|
{
|
|
170
166
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService__member_reservedSubdomains",
|
|
171
167
|
"memberFieldName": "reservedSubdomains"
|
|
172
168
|
},
|
|
173
|
-
{
|
|
174
|
-
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService__member_registrations",
|
|
175
|
-
"memberFieldName": "registrations"
|
|
176
|
-
},
|
|
177
169
|
{
|
|
178
170
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService__member_constructor",
|
|
179
171
|
"memberFieldName": "constructor"
|
|
@@ -206,6 +198,10 @@
|
|
|
206
198
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService__member_stopTunnel",
|
|
207
199
|
"memberFieldName": "stopTunnel"
|
|
208
200
|
},
|
|
201
|
+
{
|
|
202
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService__member_nodeToRegistration",
|
|
203
|
+
"memberFieldName": "nodeToRegistration"
|
|
204
|
+
},
|
|
209
205
|
{
|
|
210
206
|
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService__member_isValidSubdomain",
|
|
211
207
|
"memberFieldName": "isValidSubdomain"
|
|
@@ -242,9 +238,9 @@
|
|
|
242
238
|
}
|
|
243
239
|
},
|
|
244
240
|
{
|
|
245
|
-
"keyRaw": "
|
|
241
|
+
"keyRaw": "edgeNodeRepo",
|
|
246
242
|
"value": {
|
|
247
|
-
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#
|
|
243
|
+
"@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService_options_edgeNodeRepo"
|
|
248
244
|
}
|
|
249
245
|
},
|
|
250
246
|
{
|