@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.
Files changed (305) hide show
  1. package/README.md +141 -2
  2. package/config/cli.json +9 -71
  3. package/config/cloud.json +34 -7
  4. package/config/local.json +6 -2
  5. package/config/resolver.json +11 -49
  6. package/config/runtime-open.json +22 -0
  7. package/config/xpod.base.json +32 -0
  8. package/config/xpod.cluster.json +2 -44
  9. package/config/xpod.json +5 -2
  10. package/dist/api/auth/AuthContext.d.ts +12 -1
  11. package/dist/api/auth/AuthContext.js +18 -1
  12. package/dist/api/auth/AuthContext.js.map +1 -1
  13. package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
  14. package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
  15. package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
  16. package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
  17. package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
  18. package/dist/api/auth/index.d.ts +1 -0
  19. package/dist/api/auth/index.js +1 -0
  20. package/dist/api/auth/index.js.map +1 -1
  21. package/dist/api/chatkit/ai-provider.d.ts +0 -10
  22. package/dist/api/chatkit/ai-provider.js +11 -120
  23. package/dist/api/chatkit/ai-provider.js.map +1 -1
  24. package/dist/api/chatkit/default-agent.js +11 -8
  25. package/dist/api/chatkit/default-agent.js.map +1 -1
  26. package/dist/api/chatkit/pod-store.js +19 -3
  27. package/dist/api/chatkit/pod-store.js.map +1 -1
  28. package/dist/api/chatkit/schema.d.ts +9 -3
  29. package/dist/api/chatkit/schema.js +14 -6
  30. package/dist/api/chatkit/schema.js.map +1 -1
  31. package/dist/api/container/business-token.d.ts +9 -0
  32. package/dist/api/container/business-token.js +32 -0
  33. package/dist/api/container/business-token.js.map +1 -0
  34. package/dist/api/container/cloud.js +36 -12
  35. package/dist/api/container/cloud.js.map +1 -1
  36. package/dist/api/container/common.js +12 -5
  37. package/dist/api/container/common.js.map +1 -1
  38. package/dist/api/container/index.js +94 -14
  39. package/dist/api/container/index.js.map +1 -1
  40. package/dist/api/container/local.js +2 -1
  41. package/dist/api/container/local.js.map +1 -1
  42. package/dist/api/container/routes.js +81 -9
  43. package/dist/api/container/routes.js.map +1 -1
  44. package/dist/api/container/types.d.ts +8 -6
  45. package/dist/api/container/types.js.map +1 -1
  46. package/dist/api/handlers/AdminHandler.js +9 -9
  47. package/dist/api/handlers/AdminHandler.js.map +1 -1
  48. package/dist/api/handlers/ApiKeyHandler.js +0 -6
  49. package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
  50. package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
  51. package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
  52. package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
  53. package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
  54. package/dist/api/handlers/PodManagementHandler.js +11 -10
  55. package/dist/api/handlers/PodManagementHandler.js.map +1 -1
  56. package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
  57. package/dist/api/handlers/ProvisionHandler.js +161 -0
  58. package/dist/api/handlers/ProvisionHandler.js.map +1 -0
  59. package/dist/api/handlers/QuotaHandler.d.ts +7 -7
  60. package/dist/api/handlers/QuotaHandler.js +143 -73
  61. package/dist/api/handlers/QuotaHandler.js.map +1 -1
  62. package/dist/api/handlers/SubdomainClientHandler.js +2 -2
  63. package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
  64. package/dist/api/handlers/SubdomainHandler.js +13 -8
  65. package/dist/api/handlers/SubdomainHandler.js.map +1 -1
  66. package/dist/api/handlers/UsageHandler.d.ts +14 -0
  67. package/dist/api/handlers/UsageHandler.js +123 -0
  68. package/dist/api/handlers/UsageHandler.js.map +1 -0
  69. package/dist/api/handlers/index.d.ts +3 -1
  70. package/dist/api/handlers/index.js +3 -1
  71. package/dist/api/handlers/index.js.map +1 -1
  72. package/dist/api/main.js +18 -0
  73. package/dist/api/main.js.map +1 -1
  74. package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
  75. package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
  76. package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
  77. package/dist/api/runtime.d.ts +15 -0
  78. package/dist/api/runtime.js +104 -0
  79. package/dist/api/runtime.js.map +1 -0
  80. package/dist/api/service/VercelChatService.d.ts +16 -7
  81. package/dist/api/service/VercelChatService.js +98 -178
  82. package/dist/api/service/VercelChatService.js.map +1 -1
  83. package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
  84. package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
  85. package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
  86. package/dist/authorization/AuthModeSelector.d.ts +10 -0
  87. package/dist/authorization/AuthModeSelector.js +27 -0
  88. package/dist/authorization/AuthModeSelector.js.map +1 -0
  89. package/dist/authorization/AuthModeSelector.jsonld +81 -0
  90. package/dist/cli/commands/account.d.ts +6 -0
  91. package/dist/cli/commands/account.js +119 -0
  92. package/dist/cli/commands/account.js.map +1 -0
  93. package/dist/cli/commands/auth.js +20 -29
  94. package/dist/cli/commands/auth.js.map +1 -1
  95. package/dist/cli/commands/backup.d.ts +15 -0
  96. package/dist/cli/commands/backup.js +286 -0
  97. package/dist/cli/commands/backup.js.map +1 -0
  98. package/dist/cli/commands/config.d.ts +34 -3
  99. package/dist/cli/commands/config.js +195 -258
  100. package/dist/cli/commands/config.js.map +1 -1
  101. package/dist/cli/commands/doctor.d.ts +6 -0
  102. package/dist/cli/commands/doctor.js +94 -0
  103. package/dist/cli/commands/doctor.js.map +1 -0
  104. package/dist/cli/commands/pod.d.ts +6 -0
  105. package/dist/cli/commands/pod.js +124 -0
  106. package/dist/cli/commands/pod.js.map +1 -0
  107. package/dist/cli/commands/start.js +28 -5
  108. package/dist/cli/commands/start.js.map +1 -1
  109. package/dist/cli/index.js +9 -0
  110. package/dist/cli/index.js.map +1 -1
  111. package/dist/cli/lib/credentials-store.d.ts +17 -0
  112. package/dist/cli/lib/credentials-store.js +73 -0
  113. package/dist/cli/lib/credentials-store.js.map +1 -0
  114. package/dist/cli/lib/css-account.d.ts +17 -0
  115. package/dist/cli/lib/css-account.js +56 -0
  116. package/dist/cli/lib/css-account.js.map +1 -1
  117. package/dist/cli/lib/pod-thread-store.d.ts +57 -0
  118. package/dist/cli/lib/pod-thread-store.js +310 -0
  119. package/dist/cli/lib/pod-thread-store.js.map +1 -0
  120. package/dist/cli/lib/solid-auth.d.ts +20 -0
  121. package/dist/cli/lib/solid-auth.js +70 -0
  122. package/dist/cli/lib/solid-auth.js.map +1 -0
  123. package/dist/components/components.jsonld +5 -8
  124. package/dist/components/context.jsonld +114 -244
  125. package/dist/edge/EdgeNodeAgent.js +2 -2
  126. package/dist/edge/EdgeNodeAgent.js.map +1 -1
  127. package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
  128. package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
  129. package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
  130. package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
  131. package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
  132. package/dist/edge/EdgeNodeModeDetector.js +9 -11
  133. package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
  134. package/dist/http/ClusterIngressRouter.js +3 -3
  135. package/dist/http/ClusterIngressRouter.js.map +1 -1
  136. package/dist/http/ClusterWebSocketConfigurator.js +2 -2
  137. package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
  138. package/dist/http/PodRoutingHttpHandler.js +2 -2
  139. package/dist/http/PodRoutingHttpHandler.js.map +1 -1
  140. package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
  141. package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
  142. package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
  143. package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
  144. package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
  145. package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
  146. package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
  147. package/dist/identity/drizzle/ServiceTokenRepository.js +143 -0
  148. package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
  149. package/dist/identity/drizzle/db.d.ts +9 -0
  150. package/dist/identity/drizzle/db.js +208 -1
  151. package/dist/identity/drizzle/db.js.map +1 -1
  152. package/dist/identity/drizzle/schema.pg.d.ts +5 -0
  153. package/dist/identity/drizzle/schema.pg.js +49 -20
  154. package/dist/identity/drizzle/schema.pg.js.map +1 -1
  155. package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
  156. package/dist/identity/drizzle/schema.sqlite.js +48 -18
  157. package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
  158. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
  159. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
  160. package/dist/index.d.ts +6 -9
  161. package/dist/index.js +12 -14
  162. package/dist/index.js.map +1 -1
  163. package/dist/main.js +25 -8
  164. package/dist/main.js.map +1 -1
  165. package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
  166. package/dist/provision/ProvisionCodeCodec.js +65 -0
  167. package/dist/provision/ProvisionCodeCodec.js.map +1 -0
  168. package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
  169. package/dist/provision/ProvisionPodCreator.d.ts +20 -0
  170. package/dist/provision/ProvisionPodCreator.js +84 -0
  171. package/dist/provision/ProvisionPodCreator.js.map +1 -0
  172. package/dist/provision/ProvisionPodCreator.jsonld +118 -0
  173. package/dist/quota/DrizzleQuotaService.d.ts +17 -3
  174. package/dist/quota/DrizzleQuotaService.js +108 -8
  175. package/dist/quota/DrizzleQuotaService.js.map +1 -1
  176. package/dist/quota/DrizzleQuotaService.jsonld +33 -22
  177. package/dist/quota/NoopQuotaService.d.ts +7 -1
  178. package/dist/quota/NoopQuotaService.js +12 -0
  179. package/dist/quota/NoopQuotaService.js.map +1 -1
  180. package/dist/quota/NoopQuotaService.jsonld +24 -0
  181. package/dist/quota/QuotaService.d.ts +17 -0
  182. package/dist/quota/QuotaService.js +5 -0
  183. package/dist/quota/QuotaService.js.map +1 -1
  184. package/dist/quota/QuotaService.jsonld +50 -0
  185. package/dist/runtime/Proxy.d.ts +22 -4
  186. package/dist/runtime/Proxy.js +154 -35
  187. package/dist/runtime/Proxy.js.map +1 -1
  188. package/dist/runtime/XpodRuntime.d.ts +49 -0
  189. package/dist/runtime/XpodRuntime.js +374 -0
  190. package/dist/runtime/XpodRuntime.js.map +1 -0
  191. package/dist/runtime/env-utils.d.ts +2 -0
  192. package/dist/runtime/env-utils.js +55 -0
  193. package/dist/runtime/env-utils.js.map +1 -0
  194. package/dist/runtime/index.d.ts +4 -0
  195. package/dist/runtime/index.js +8 -1
  196. package/dist/runtime/index.js.map +1 -1
  197. package/dist/runtime/socket-fetch.d.ts +1 -0
  198. package/dist/runtime/socket-fetch.js +72 -0
  199. package/dist/runtime/socket-fetch.js.map +1 -0
  200. package/dist/runtime/socket-http.d.ts +1 -0
  201. package/dist/runtime/socket-http.js +142 -0
  202. package/dist/runtime/socket-http.js.map +1 -0
  203. package/dist/runtime/socket-utils.d.ts +2 -0
  204. package/dist/runtime/socket-utils.js +34 -0
  205. package/dist/runtime/socket-utils.js.map +1 -0
  206. package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
  207. package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
  208. package/dist/service/EdgeNodeSignalClient.js.map +1 -0
  209. package/dist/service/PodMigrationService.d.ts +1 -2
  210. package/dist/service/PodMigrationService.js +1 -2
  211. package/dist/service/PodMigrationService.js.map +1 -1
  212. package/dist/storage/SparqlUpdateResourceStore.js +1 -1
  213. package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
  214. package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
  215. package/dist/storage/accessors/MinioDataAccessor.js +10 -0
  216. package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
  217. package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
  218. package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
  219. package/dist/storage/accessors/MixDataAccessor.js +12 -1
  220. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  221. package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
  222. package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
  223. package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
  224. package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
  225. package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
  226. package/dist/storage/quota/UsageRepository.d.ts +41 -8
  227. package/dist/storage/quota/UsageRepository.js +252 -50
  228. package/dist/storage/quota/UsageRepository.js.map +1 -1
  229. package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
  230. package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
  231. package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
  232. package/dist/storage/sparql/QueryOptimizer.js +13 -1
  233. package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
  234. package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
  235. package/dist/storage/sparql/QuintQuerySource.js +152 -1
  236. package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
  237. package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
  238. package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
  239. package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
  240. package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
  241. package/dist/subdomain/SubdomainClient.d.ts +3 -3
  242. package/dist/subdomain/SubdomainClient.js +1 -1
  243. package/dist/subdomain/SubdomainClient.js.map +1 -1
  244. package/dist/subdomain/SubdomainService.d.ts +15 -16
  245. package/dist/subdomain/SubdomainService.js +80 -54
  246. package/dist/subdomain/SubdomainService.js.map +1 -1
  247. package/dist/subdomain/SubdomainService.jsonld +22 -26
  248. package/dist/supervisor/Supervisor.d.ts +7 -2
  249. package/dist/supervisor/Supervisor.js +33 -1
  250. package/dist/supervisor/Supervisor.js.map +1 -1
  251. package/dist/test-utils/index.d.ts +4 -0
  252. package/dist/test-utils/index.js +8 -0
  253. package/dist/test-utils/index.js.map +1 -0
  254. package/dist/test-utils/no-auth-xpod.d.ts +11 -0
  255. package/dist/test-utils/no-auth-xpod.js +25 -0
  256. package/dist/test-utils/no-auth-xpod.js.map +1 -0
  257. package/dist/test-utils/seed-pod.d.ts +5 -0
  258. package/dist/test-utils/seed-pod.js +61 -0
  259. package/dist/test-utils/seed-pod.js.map +1 -0
  260. package/package.json +23 -5
  261. package/templates/identity/account/create-pod.html.ejs +110 -0
  262. package/templates/main.html.ejs +10 -0
  263. package/dist/api/handlers/DevHandler.d.ts +0 -18
  264. package/dist/api/handlers/DevHandler.js +0 -276
  265. package/dist/api/handlers/DevHandler.js.map +0 -1
  266. package/dist/api/handlers/SignalHandler.d.ts +0 -13
  267. package/dist/api/handlers/SignalHandler.js +0 -122
  268. package/dist/api/handlers/SignalHandler.js.map +0 -1
  269. package/dist/gateway/Proxy.d.ts +0 -24
  270. package/dist/gateway/Proxy.js +0 -209
  271. package/dist/gateway/Proxy.js.map +0 -1
  272. package/dist/gateway/Supervisor.d.ts +0 -2
  273. package/dist/gateway/Supervisor.js +0 -7
  274. package/dist/gateway/Supervisor.js.map +0 -1
  275. package/dist/gateway/port-finder.d.ts +0 -4
  276. package/dist/gateway/port-finder.js +0 -15
  277. package/dist/gateway/port-finder.js.map +0 -1
  278. package/dist/gateway/types.d.ts +0 -1
  279. package/dist/gateway/types.js +0 -3
  280. package/dist/gateway/types.js.map +0 -1
  281. package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
  282. package/dist/http/SignalInterceptHttpHandler.js +0 -47
  283. package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
  284. package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
  285. package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
  286. package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
  287. package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
  288. package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
  289. package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
  290. package/dist/quota/DefaultQuotaService.d.ts +0 -16
  291. package/dist/quota/DefaultQuotaService.js +0 -37
  292. package/dist/quota/DefaultQuotaService.js.map +0 -1
  293. package/dist/quota/DefaultQuotaService.jsonld +0 -85
  294. package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
  295. package/dist/service/PodMigrationService.jsonld +0 -76
  296. package/dist/storage/MigratableDataAccessor.d.ts +0 -63
  297. package/dist/storage/MigratableDataAccessor.js +0 -11
  298. package/dist/storage/MigratableDataAccessor.js.map +0 -1
  299. package/dist/storage/MigratableDataAccessor.jsonld +0 -60
  300. package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
  301. package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
  302. package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
  303. package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
  304. package/static/app/assets/index.css +0 -1
  305. package/static/app/assets/main.js +0 -11
@@ -1 +1 @@
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 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
- publicIp?: string;
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
- publicIp?: string;
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
- connectivityCheckEndpoint?: string;
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 connectivityCheckEndpoint?;
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
- publicIp?: string;
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.connectivityCheckEndpoint = options.connectivityCheckEndpoint;
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
- // 3. 已注册检查
46
- if (this.registrations.has(subdomain.toLowerCase())) {
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, publicIp, ownerId } = options;
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
- // 2. 连通性检测 (如果提供了公网 IP)
63
+ // 连通性检测
67
64
  let mode = 'tunnel';
68
65
  let verifiedIp;
69
- if (publicIp) {
70
- const connectivity = await this.checkConnectivity(publicIp, localPort);
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 = publicIp;
70
+ verifiedIp = ipv4;
76
71
  }
77
72
  }
78
- // 3. 根据模式设置 DNS 和隧道
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
- // 4. 保存注册信息
99
- const registration = {
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
- publicIp: verifiedIp,
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 registration = this.registrations.get(normalizedSubdomain);
117
- if (!registration) {
115
+ const node = await this.edgeNodeRepo.findNodeBySubdomain(normalizedSubdomain);
116
+ if (!node) {
118
117
  throw new Error('Subdomain not found');
119
118
  }
120
- // 1. 清理隧道
121
- if (registration.tunnelConfig) {
122
- await this.tunnelProvider.cleanup(registration.tunnelConfig);
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: registration.mode === 'direct' ? 'A' : 'CNAME',
127
+ type: accessMode === 'direct' ? 'A' : 'CNAME',
130
128
  });
131
129
  }
132
- catch (error) {
133
- console.warn(`Failed to delete DNS record for ${normalizedSubdomain}:`, error);
130
+ catch {
131
+ // DNS 删除失败不阻塞释放
134
132
  }
135
- // 3. 移除注册信息
136
- this.registrations.delete(normalizedSubdomain);
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
- return this.registrations.get(subdomain.toLowerCase());
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
- return Array.from(this.registrations.values());
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 registration = this.registrations.get(subdomain.toLowerCase());
155
- if (!registration) {
178
+ const reg = await this.getRegistration(subdomain);
179
+ if (!reg) {
156
180
  throw new Error('Subdomain not found');
157
181
  }
158
- if (registration.mode !== 'tunnel' || !registration.tunnelConfig) {
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(registration.tunnelConfig);
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
- publicIp: ip,
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":"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","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#SubdomainRegistration__member_publicIp",
28
- "memberFieldName": "publicIp"
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#ConnectivityResult__member_publicIp",
58
- "memberFieldName": "publicIp"
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#SubdomainServiceOptions__member_connectivityCheckEndpoint",
92
- "memberFieldName": "connectivityCheckEndpoint"
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#SubdomainService_options_connectivityCheckEndpoint",
124
- "range": {
125
- "@type": "ParameterRangeUnion",
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#SubdomainService__member_connectivityCheckEndpoint",
167
- "memberFieldName": "connectivityCheckEndpoint"
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": "connectivityCheckEndpoint",
241
+ "keyRaw": "edgeNodeRepo",
246
242
  "value": {
247
- "@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService_options_connectivityCheckEndpoint"
243
+ "@id": "undefineds:dist/subdomain/SubdomainService.jsonld#SubdomainService_options_edgeNodeRepo"
248
244
  }
249
245
  },
250
246
  {