@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
|
@@ -9,7 +9,6 @@ class EdgeNodeDnsCoordinator {
|
|
|
9
9
|
this.rootDomain = this.normalizeRootDomain(options.rootDomain);
|
|
10
10
|
this.defaultRecordType = options.defaultRecordType ?? 'A';
|
|
11
11
|
this.ttl = this.normalizeTtl(options.ttl);
|
|
12
|
-
this.clusterIp = this.extractString(options.clusterIp);
|
|
13
12
|
this.enabled = Boolean(this.rootDomain);
|
|
14
13
|
}
|
|
15
14
|
async synchronize(nodeId, metadata) {
|
|
@@ -17,55 +16,47 @@ class EdgeNodeDnsCoordinator {
|
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
19
18
|
const hints = this.extractDnsHints(metadata);
|
|
20
|
-
// Extract subdomain and access mode
|
|
21
19
|
const subdomain = this.extractString(metadata.subdomain) ?? hints?.subdomain;
|
|
22
20
|
if (!subdomain) {
|
|
23
21
|
this.logger.debug(`Node ${nodeId} 未提供 subdomain,跳过 DNS 同步。`);
|
|
24
22
|
return;
|
|
25
23
|
}
|
|
26
|
-
|
|
27
|
-
const normalizedAccessMode = accessMode?.trim().toLowerCase();
|
|
28
|
-
// Determine DNS target based on access mode
|
|
24
|
+
// 用节点上报的地址(公网 IP 或隧道入口,由节点自行决定)
|
|
29
25
|
let target;
|
|
30
26
|
let recordType;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (ipv6 && this.isIpv6(ipv6)) {
|
|
40
|
-
target = ipv6;
|
|
41
|
-
recordType = 'AAAA';
|
|
42
|
-
this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
target = publicIp ?? ipv4 ?? publicAddress;
|
|
46
|
-
}
|
|
47
|
-
if (!target && hints?.target) {
|
|
48
|
-
target = hints.target;
|
|
49
|
-
}
|
|
50
|
-
if (!target) {
|
|
51
|
-
this.logger.warn(`Node ${nodeId} (direct mode) 未提供公网 IP,跳过 DNS 同步。`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
27
|
+
const ipv6 = this.extractString(metadata.ipv6);
|
|
28
|
+
const ipv4 = this.extractString(metadata.ipv4);
|
|
29
|
+
const publicAddress = this.extractString(metadata.publicAddress);
|
|
30
|
+
// IPv6 优先
|
|
31
|
+
if (ipv6 && this.isIpv6(ipv6)) {
|
|
32
|
+
target = ipv6;
|
|
33
|
+
recordType = 'AAAA';
|
|
34
|
+
this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);
|
|
54
35
|
}
|
|
55
|
-
else
|
|
56
|
-
|
|
57
|
-
if (!this.clusterIp) {
|
|
58
|
-
this.logger.debug(`Cluster IP 未配置,跳过 proxy 模式节点 ${nodeId} 的 DNS 同步。`);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
target = this.clusterIp;
|
|
36
|
+
else {
|
|
37
|
+
target = ipv4 ?? publicAddress;
|
|
62
38
|
}
|
|
63
|
-
|
|
64
|
-
// Fallback: 使用旧的逻辑从 metadata 提取
|
|
39
|
+
if (!target && hints?.target) {
|
|
65
40
|
target = hints.target;
|
|
66
41
|
}
|
|
67
|
-
|
|
68
|
-
this.logger.debug(`Node ${nodeId}
|
|
42
|
+
if (!target) {
|
|
43
|
+
this.logger.debug(`Node ${nodeId} 未提供可用地址,跳过 DNS 同步。`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// 健康检查未通过时删除 DNS 记录
|
|
47
|
+
const connectivityStatus = this.extractString(metadata.connectivityStatus);
|
|
48
|
+
if (connectivityStatus === 'unreachable') {
|
|
49
|
+
this.logger.info(`节点 ${nodeId} 不可达,删除 DNS 记录 ${subdomain}.${this.rootDomain}`);
|
|
50
|
+
try {
|
|
51
|
+
await this.provider.deleteRecord({
|
|
52
|
+
domain: this.rootDomain,
|
|
53
|
+
subdomain,
|
|
54
|
+
type: recordType ?? this.defaultRecordType,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.logger.error(`删除节点 ${nodeId} DNS 记录失败: ${error.message}`);
|
|
59
|
+
}
|
|
69
60
|
return;
|
|
70
61
|
}
|
|
71
62
|
const type = recordType ?? this.detectRecordType(target) ?? this.defaultRecordType;
|
|
@@ -82,8 +73,7 @@ class EdgeNodeDnsCoordinator {
|
|
|
82
73
|
value,
|
|
83
74
|
ttl: this.ttl,
|
|
84
75
|
});
|
|
85
|
-
|
|
86
|
-
this.logger.info(`已同步节点 ${nodeId} (${loggedMode}) 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);
|
|
76
|
+
this.logger.info(`已同步节点 ${nodeId} 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);
|
|
87
77
|
}
|
|
88
78
|
catch (error) {
|
|
89
79
|
this.logger.error(`同步节点 ${nodeId} DNS 记录失败: ${error.message}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeDnsCoordinator.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeDnsCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\nimport type { DnsProvider, DnsRecordTypeValue } from '../dns/DnsProvider';\n\nexport interface EdgeNodeDnsCoordinatorOptions {\n provider: DnsProvider;\n /** 顶级域名,例如 `xpod.example`。 */\n rootDomain?: string | null;\n /**\n * 默认记录类型,当目标地址无法识别时回退使用。\n * 一般为 `A`(IPv4)或 `AAAA`(IPv6)。\n */\n defaultRecordType?: DnsRecordTypeValue;\n /** TTL 秒数,缺省按供应商默认。 */\n ttl?: number | string | null;\n /**\n * Cluster 的公网 IP 地址,用于 proxy 模式的 DNS 指向。\n * 如果未设置,proxy 模式节点将跳过 DNS 同步。\n */\n clusterIp?: string | null;\n}\n\nexport class EdgeNodeDnsCoordinator {\n private readonly logger = getLoggerFor(this);\n private readonly provider: DnsProvider;\n private readonly rootDomain?: string;\n private readonly defaultRecordType: DnsRecordTypeValue;\n private readonly ttl?: number;\n private readonly clusterIp?: string;\n private readonly enabled: boolean;\n\n public constructor(options: EdgeNodeDnsCoordinatorOptions) {\n this.provider = options.provider;\n this.rootDomain = this.normalizeRootDomain(options.rootDomain);\n this.defaultRecordType = options.defaultRecordType ?? 'A';\n this.ttl = this.normalizeTtl(options.ttl);\n this.clusterIp = this.extractString(options.clusterIp);\n this.enabled = Boolean(this.rootDomain);\n }\n\n public async synchronize(nodeId: string, metadata: Record<string, unknown>): Promise<void> {\n if (!this.enabled) {\n return;\n }\n \n const hints = this.extractDnsHints(metadata);\n\n // Extract subdomain and access mode\n const subdomain = this.extractString(metadata.subdomain) ?? hints?.subdomain;\n if (!subdomain) {\n this.logger.debug(`Node ${nodeId} 未提供 subdomain,跳过 DNS 同步。`);\n return;\n }\n \n const accessMode = this.extractString(metadata.accessMode);\n const normalizedAccessMode = accessMode?.trim().toLowerCase();\n \n // Determine DNS target based on access mode\n let target: string | undefined;\n let recordType: DnsRecordTypeValue | undefined;\n \n if (normalizedAccessMode === 'direct') {\n // Direct mode: DNS 指向节点公网 IP\n // 优先使用 IPv6(如果有),因为可以避免 NAT 问题\n const ipv6 = this.extractString(metadata.ipv6);\n const ipv4 = this.extractString(metadata.ipv4);\n const publicIp = this.extractString(metadata.publicIp);\n const publicAddress = this.extractString(metadata.publicAddress);\n \n // IPv6 优先策略:如果有公网 IPv6,优先使用\n if (ipv6 && this.isIpv6(ipv6)) {\n target = ipv6;\n recordType = 'AAAA';\n this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);\n } else {\n target = publicIp ?? ipv4 ?? publicAddress;\n }\n \n if (!target && hints?.target) {\n target = hints.target;\n }\n\n if (!target) {\n this.logger.warn(`Node ${nodeId} (direct mode) 未提供公网 IP,跳过 DNS 同步。`);\n return;\n }\n } else if (normalizedAccessMode === 'proxy') {\n // Proxy mode: DNS 指向 Cluster IP\n if (!this.clusterIp) {\n this.logger.debug(`Cluster IP 未配置,跳过 proxy 模式节点 ${nodeId} 的 DNS 同步。`);\n return;\n }\n target = this.clusterIp;\n } else if (hints?.target) {\n // Fallback: 使用旧的逻辑从 metadata 提取\n target = hints.target;\n } else {\n this.logger.debug(`Node ${nodeId} 未提供 accessMode/dns hints,跳过同步。`);\n return;\n }\n \n const type = recordType ?? this.detectRecordType(target) ?? this.defaultRecordType;\n const value = this.normalizeRecordValue(target, type);\n\n if (!value) {\n this.logger.warn(`Edge node ${nodeId} DNS 目标解析失败,跳过同步。`);\n return;\n }\n\n try {\n await this.provider.upsertRecord({\n domain: this.rootDomain!,\n subdomain,\n type,\n value,\n ttl: this.ttl,\n });\n const loggedMode = normalizedAccessMode ?? 'unknown';\n this.logger.info(`已同步节点 ${nodeId} (${loggedMode}) 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);\n } catch (error: unknown) {\n this.logger.error(`同步节点 ${nodeId} DNS 记录失败: ${(error as Error).message}`);\n throw error;\n }\n }\n\n private extractDnsHints(metadata: Record<string, unknown>): { subdomain: string; target: string } | undefined {\n const dns = metadata?.dns;\n if (!dns || typeof dns !== 'object') {\n return undefined;\n }\n const subdomain = this.extractString((dns as Record<string, unknown>).subdomain);\n if (!subdomain) {\n return undefined;\n }\n\n const target = this.extractString((dns as Record<string, unknown>).target)\n ?? this.extractString(metadata.publicAddress)\n ?? this.extractString(metadata.baseUrl);\n\n if (!target) {\n return undefined;\n }\n return { subdomain, target };\n }\n\n private extractString(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private detectRecordType(target: string): DnsRecordTypeValue | undefined {\n const host = this.extractHost(target) ?? target;\n if (this.isIpv4(host)) {\n return 'A';\n }\n if (this.isIpv6(host)) {\n return 'AAAA';\n }\n if (host.includes('.')) {\n return 'CNAME';\n }\n return undefined;\n }\n\n private normalizeRecordValue(target: string, type: DnsRecordTypeValue): string | undefined {\n const host = this.extractHost(target) ?? target;\n if (type === 'A' && this.isIpv4(host)) {\n return host;\n }\n if (type === 'AAAA' && this.isIpv6(host)) {\n return host;\n }\n if (type === 'CNAME') {\n return host.endsWith('.') ? host : `${host}.`;\n }\n if (type === 'TXT') {\n return host;\n }\n return undefined;\n }\n\n private extractHost(input: string): string | undefined {\n try {\n const url = new URL(input);\n return url.hostname;\n } catch {\n return input;\n }\n }\n\n private isIpv4(value: string): boolean {\n const parts = value.split('.');\n if (parts.length !== 4) {\n return false;\n }\n return parts.every((part) => {\n if (!/^[0-9]{1,3}$/.test(part)) {\n return false;\n }\n const num = Number(part);\n return num >= 0 && num <= 255;\n });\n }\n\n private isIpv6(value: string): boolean {\n return /^[0-9a-fA-F:]+$/.test(value) && value.includes(':');\n }\n\n private normalizeRootDomain(value?: string | null): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n let trimmed = value.trim();\n if (trimmed.includes('://')) {\n try {\n trimmed = new URL(trimmed).hostname;\n } catch {\n // ignore\n }\n }\n trimmed = trimmed.replace(/\\.$/, '');\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private normalizeTtl(value?: number | string | null): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) {\n return Math.trunc(value);\n }\n if (typeof value === 'string') {\n const parsed = Number(value.trim());\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.trunc(parsed);\n }\n }\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeDnsCoordinator.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeDnsCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\nimport type { DnsProvider, DnsRecordTypeValue } from '../dns/DnsProvider';\n\nexport interface EdgeNodeDnsCoordinatorOptions {\n provider: DnsProvider;\n /** 顶级域名,例如 `undefineds.site`。 */\n rootDomain?: string | null;\n /**\n * 默认记录类型,当目标地址无法识别时回退使用。\n * 一般为 `A`(IPv4)或 `AAAA`(IPv6)。\n */\n defaultRecordType?: DnsRecordTypeValue;\n /** TTL 秒数,缺省按供应商默认。 */\n ttl?: number | string | null;\n}\n\nexport class EdgeNodeDnsCoordinator {\n private readonly logger = getLoggerFor(this);\n private readonly provider: DnsProvider;\n private readonly rootDomain?: string;\n private readonly defaultRecordType: DnsRecordTypeValue;\n private readonly ttl?: number;\n private readonly enabled: boolean;\n\n public constructor(options: EdgeNodeDnsCoordinatorOptions) {\n this.provider = options.provider;\n this.rootDomain = this.normalizeRootDomain(options.rootDomain);\n this.defaultRecordType = options.defaultRecordType ?? 'A';\n this.ttl = this.normalizeTtl(options.ttl);\n this.enabled = Boolean(this.rootDomain);\n }\n\n public async synchronize(nodeId: string, metadata: Record<string, unknown>): Promise<void> {\n if (!this.enabled) {\n return;\n }\n\n const hints = this.extractDnsHints(metadata);\n\n const subdomain = this.extractString(metadata.subdomain) ?? hints?.subdomain;\n if (!subdomain) {\n this.logger.debug(`Node ${nodeId} 未提供 subdomain,跳过 DNS 同步。`);\n return;\n }\n\n // 用节点上报的地址(公网 IP 或隧道入口,由节点自行决定)\n let target: string | undefined;\n let recordType: DnsRecordTypeValue | undefined;\n\n const ipv6 = this.extractString(metadata.ipv6);\n const ipv4 = this.extractString(metadata.ipv4);\n const publicAddress = this.extractString(metadata.publicAddress);\n\n // IPv6 优先\n if (ipv6 && this.isIpv6(ipv6)) {\n target = ipv6;\n recordType = 'AAAA';\n this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);\n } else {\n target = ipv4 ?? publicAddress;\n }\n\n if (!target && hints?.target) {\n target = hints.target;\n }\n\n if (!target) {\n this.logger.debug(`Node ${nodeId} 未提供可用地址,跳过 DNS 同步。`);\n return;\n }\n\n // 健康检查未通过时删除 DNS 记录\n const connectivityStatus = this.extractString(metadata.connectivityStatus);\n if (connectivityStatus === 'unreachable') {\n this.logger.info(`节点 ${nodeId} 不可达,删除 DNS 记录 ${subdomain}.${this.rootDomain}`);\n try {\n await this.provider.deleteRecord({\n domain: this.rootDomain!,\n subdomain,\n type: recordType ?? this.defaultRecordType,\n });\n } catch (error: unknown) {\n this.logger.error(`删除节点 ${nodeId} DNS 记录失败: ${(error as Error).message}`);\n }\n return;\n }\n\n const type = recordType ?? this.detectRecordType(target) ?? this.defaultRecordType;\n const value = this.normalizeRecordValue(target, type);\n\n if (!value) {\n this.logger.warn(`Edge node ${nodeId} DNS 目标解析失败,跳过同步。`);\n return;\n }\n\n try {\n await this.provider.upsertRecord({\n domain: this.rootDomain!,\n subdomain,\n type,\n value,\n ttl: this.ttl,\n });\n this.logger.info(`已同步节点 ${nodeId} 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);\n } catch (error: unknown) {\n this.logger.error(`同步节点 ${nodeId} DNS 记录失败: ${(error as Error).message}`);\n throw error;\n }\n }\n\n private extractDnsHints(metadata: Record<string, unknown>): { subdomain: string; target: string } | undefined {\n const dns = metadata?.dns;\n if (!dns || typeof dns !== 'object') {\n return undefined;\n }\n const subdomain = this.extractString((dns as Record<string, unknown>).subdomain);\n if (!subdomain) {\n return undefined;\n }\n\n const target = this.extractString((dns as Record<string, unknown>).target)\n ?? this.extractString(metadata.publicAddress)\n ?? this.extractString(metadata.baseUrl);\n\n if (!target) {\n return undefined;\n }\n return { subdomain, target };\n }\n\n private extractString(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private detectRecordType(target: string): DnsRecordTypeValue | undefined {\n const host = this.extractHost(target) ?? target;\n if (this.isIpv4(host)) {\n return 'A';\n }\n if (this.isIpv6(host)) {\n return 'AAAA';\n }\n if (host.includes('.')) {\n return 'CNAME';\n }\n return undefined;\n }\n\n private normalizeRecordValue(target: string, type: DnsRecordTypeValue): string | undefined {\n const host = this.extractHost(target) ?? target;\n if (type === 'A' && this.isIpv4(host)) {\n return host;\n }\n if (type === 'AAAA' && this.isIpv6(host)) {\n return host;\n }\n if (type === 'CNAME') {\n return host.endsWith('.') ? host : `${host}.`;\n }\n if (type === 'TXT') {\n return host;\n }\n return undefined;\n }\n\n private extractHost(input: string): string | undefined {\n try {\n const url = new URL(input);\n return url.hostname;\n } catch {\n return input;\n }\n }\n\n private isIpv4(value: string): boolean {\n const parts = value.split('.');\n if (parts.length !== 4) {\n return false;\n }\n return parts.every((part) => {\n if (!/^[0-9]{1,3}$/.test(part)) {\n return false;\n }\n const num = Number(part);\n return num >= 0 && num <= 255;\n });\n }\n\n private isIpv6(value: string): boolean {\n return /^[0-9a-fA-F:]+$/.test(value) && value.includes(':');\n }\n\n private normalizeRootDomain(value?: string | null): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n let trimmed = value.trim();\n if (trimmed.includes('://')) {\n try {\n trimmed = new URL(trimmed).hostname;\n } catch {\n // ignore\n }\n }\n trimmed = trimmed.replace(/\\.$/, '');\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private normalizeTtl(value?: number | string | null): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) {\n return Math.trunc(value);\n }\n if (typeof value === 'string') {\n const parsed = Number(value.trim());\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.trunc(parsed);\n }\n }\n return undefined;\n }\n}\n"]}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
}
|
|
28
28
|
]
|
|
29
29
|
},
|
|
30
|
-
"comment": "顶级域名,例如 `
|
|
30
|
+
"comment": "顶级域名,例如 `undefineds.site`。"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_defaultRecordType",
|
|
@@ -78,22 +78,6 @@
|
|
|
78
78
|
]
|
|
79
79
|
},
|
|
80
80
|
"comment": "TTL 秒数,缺省按供应商默认。"
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_clusterIp",
|
|
84
|
-
"range": {
|
|
85
|
-
"@type": "ParameterRangeUnion",
|
|
86
|
-
"parameterRangeElements": [
|
|
87
|
-
"xsd:string",
|
|
88
|
-
{
|
|
89
|
-
"@type": "ParameterRangeWildcard"
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"@type": "ParameterRangeUndefined"
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
},
|
|
96
|
-
"comment": "Cluster 的公网 IP 地址,用于 proxy 模式的 DNS 指向。 如果未设置,proxy 模式节点将跳过 DNS 同步。"
|
|
97
81
|
}
|
|
98
82
|
],
|
|
99
83
|
"memberFields": [
|
|
@@ -117,10 +101,6 @@
|
|
|
117
101
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator__member_ttl",
|
|
118
102
|
"memberFieldName": "ttl"
|
|
119
103
|
},
|
|
120
|
-
{
|
|
121
|
-
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator__member_clusterIp",
|
|
122
|
-
"memberFieldName": "clusterIp"
|
|
123
|
-
},
|
|
124
104
|
{
|
|
125
105
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator__member_enabled",
|
|
126
106
|
"memberFieldName": "enabled"
|
|
@@ -197,12 +177,6 @@
|
|
|
197
177
|
"value": {
|
|
198
178
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_ttl"
|
|
199
179
|
}
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
"keyRaw": "clusterIp",
|
|
203
|
-
"value": {
|
|
204
|
-
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_clusterIp"
|
|
205
|
-
}
|
|
206
180
|
}
|
|
207
181
|
]
|
|
208
182
|
}
|
|
@@ -38,11 +38,11 @@ class EdgeNodeModeDetector {
|
|
|
38
38
|
const supportsDirect = supportedModes.has('direct');
|
|
39
39
|
const supportsProxy = supportedModes.has('proxy');
|
|
40
40
|
// Prefer direct if supported and has public IP (IPv6 or IPv4)
|
|
41
|
-
const hasPublicIp = Boolean(nodeInfo.
|
|
41
|
+
const hasPublicIp = Boolean(nodeInfo.ipv4 || nodeInfo.publicIpv6);
|
|
42
42
|
let connectivityTest;
|
|
43
43
|
if (supportsDirect && hasPublicIp) {
|
|
44
44
|
// 优先测试 IPv6 连通性
|
|
45
|
-
const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.
|
|
45
|
+
const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.ipv4;
|
|
46
46
|
const port = nodeInfo.publicPort ?? 443;
|
|
47
47
|
connectivityTest = await this.testDirectConnectivity(ipToTest, port);
|
|
48
48
|
if (connectivityTest.success) {
|
|
@@ -55,11 +55,11 @@ class EdgeNodeModeDetector {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
// 如果 IPv6 测试失败且有 IPv4,尝试 IPv4
|
|
58
|
-
if (nodeInfo.publicIpv6 && nodeInfo.
|
|
58
|
+
if (nodeInfo.publicIpv6 && nodeInfo.ipv4) {
|
|
59
59
|
this.logger.debug(`IPv6 connectivity failed for ${nodeInfo.nodeId}, trying IPv4...`);
|
|
60
|
-
connectivityTest = await this.testDirectConnectivity(nodeInfo.
|
|
60
|
+
connectivityTest = await this.testDirectConnectivity(nodeInfo.ipv4, port);
|
|
61
61
|
if (connectivityTest.success) {
|
|
62
|
-
this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.
|
|
62
|
+
this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.ipv4}:${port}`);
|
|
63
63
|
return {
|
|
64
64
|
accessMode: 'direct',
|
|
65
65
|
reason: 'Direct connectivity test passed (IPv4 fallback)',
|
|
@@ -95,10 +95,8 @@ class EdgeNodeModeDetector {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
generateSubdomain(nodeId) {
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const sanitized = nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();
|
|
101
|
-
return `${sanitized}.${this.baseDomain}`;
|
|
98
|
+
// 只返回前缀,完整 FQDN 由 DnsCoordinator 的 rootDomain 拼接
|
|
99
|
+
return nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();
|
|
102
100
|
}
|
|
103
101
|
async testDirectConnectivity(ip, port) {
|
|
104
102
|
let lastResult = await this.singleConnectivityAttempt(ip, port);
|
|
@@ -161,10 +159,10 @@ class EdgeNodeModeDetector {
|
|
|
161
159
|
async recheckMode(currentMode, nodeInfo) {
|
|
162
160
|
// Only recheck if currently in proxy mode and public IP is available
|
|
163
161
|
const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);
|
|
164
|
-
if (currentMode !== 'proxy' || !nodeInfo.
|
|
162
|
+
if (currentMode !== 'proxy' || !nodeInfo.ipv4 || !supportedModes.has('direct')) {
|
|
165
163
|
return null;
|
|
166
164
|
}
|
|
167
|
-
const connectivityTest = await this.testDirectConnectivity(nodeInfo.
|
|
165
|
+
const connectivityTest = await this.testDirectConnectivity(nodeInfo.ipv4, nodeInfo.publicPort ?? 443);
|
|
168
166
|
if (connectivityTest.success) {
|
|
169
167
|
const subdomain = this.generateSubdomain(nodeInfo.nodeId);
|
|
170
168
|
this.logger.info(`Node ${nodeInfo.nodeId} connectivity restored, switching to direct mode`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeModeDetector.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeModeDetector.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iEAAqD;AAwCrD,MAAa,oBAAoB;IAM/B,YAAmB,OAAoC;QALtC,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAM3C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,IAAI,IAAI,CAAC;QACnE,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,qBAAqB,IAAI,CAAC,CAAC,CAAC;IAC/E,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,QAA8B;QACpD,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1D,MAAM,cAAc,GAAG,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACzE,MAAM,cAAc,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAElD,8DAA8D;QAC9D,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC;QACtE,IAAI,gBAAsF,CAAC;QAE3F,IAAI,cAAc,IAAI,WAAW,EAAE,CAAC;YAClC,gBAAgB;YAChB,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,QAAS,CAAC;YAC3D,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,IAAI,GAAG,CAAC;YAExC,gBAAgB,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAErE,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;gBAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,QAAQ,CAAC,MAAM,6BAA6B,QAAQ,IAAI,IAAI,EAAE,CAAC,CAAC;gBACzF,OAAO;oBACL,UAAU,EAAE,QAAQ;oBACpB,MAAM,EAAE,oCAAoC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG;oBACpF,SAAS;oBACT,gBAAgB;iBACjB,CAAC;YACJ,CAAC;YAED,8BAA8B;YAC9B,IAAI,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,QAAQ,CAAC,MAAM,kBAAkB,CAAC,CAAC;gBACrF,gBAAgB,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;gBAE9E,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;oBAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,QAAQ,CAAC,MAAM,sCAAsC,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC,CAAC;oBAC3G,OAAO;wBACL,UAAU,EAAE,QAAQ;wBACpB,MAAM,EAAE,iDAAiD;wBACzD,SAAS;wBACT,gBAAgB;qBACjB,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,QAAQ,CAAC,MAAM,qEAAqE,gBAAgB,CAAC,KAAK,EAAE,CAAC,CAAC;YACvI,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,OAAO;oBACL,UAAU,EAAE,QAAQ;oBACpB,MAAM,EAAE,uDAAuD,gBAAgB,CAAC,KAAK,EAAE;oBACvF,SAAS;oBACT,gBAAgB;iBACjB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO;gBACL,UAAU,EAAE,OAAO;gBACnB,MAAM,EAAE,cAAc,IAAI,WAAW,CAAC,CAAC,CAAC,yCAAyC,CAAC,CAAC,CAAC,mCAAmC;gBACvH,SAAS;gBACT,gBAAgB;aACjB,CAAC;QACJ,CAAC;QAED,kCAAkC;QAClC,OAAO;YACL,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO;YAC/C,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC,iCAAiC;YACpG,SAAS;SACV,CAAC;IACJ,CAAC;IAEO,iBAAiB,CAAC,MAAc;QACtC,+CAA+C;QAC/C,yFAAyF;QACzF,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACrE,OAAO,GAAG,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;IAC3C,CAAC;IAEO,KAAK,CAAC,sBAAsB,CAAC,EAAU,EAAE,IAAY;QAK3D,IAAI,UAAU,GAAG,MAAM,IAAI,CAAC,yBAAyB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAChE,IAAI,UAAU,CAAC,OAAO,IAAI,IAAI,CAAC,qBAAqB,KAAK,CAAC,EAAE,CAAC;YAC3D,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,qBAAqB,EAAE,OAAO,EAAE,EAAE,CAAC;YACvE,UAAU,GAAG,MAAM,IAAI,CAAC,yBAAyB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAC5D,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAEO,KAAK,CAAC,yBAAyB,CAAC,EAAU,EAAE,IAAY;QAK9D,MAAM,GAAG,GAAG,wDAAa,UAAU,GAAC,CAAC;QAErC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAEhC,MAAM,OAAO,GAAG,GAAS,EAAE;gBACzB,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,4BAA4B,IAAI,CAAC,qBAAqB,IAAI;iBAClE,CAAC,CAAC;YACL,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAE/B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;gBACxB,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;gBACvC,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC;oBACN,OAAO,EAAE,IAAI;oBACb,OAAO;iBACR,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC3B,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,KAAK,CAAC,OAAO;iBACrB,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC;gBACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC3B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC;oBACN,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B;iBAC3E,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,WAAmB,EAAE,QAA8B;QAC1E,qEAAqE;QACrE,MAAM,cAAc,GAAG,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACzE,IAAI,WAAW,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,sBAAsB,CACxD,QAAQ,CAAC,QAAQ,EACjB,QAAQ,CAAC,UAAU,IAAI,GAAG,CAC3B,CAAC;QAEF,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,QAAQ,CAAC,MAAM,kDAAkD,CAAC,CAAC;YAC5F,OAAO;gBACL,UAAU,EAAE,QAAQ;gBACpB,MAAM,EAAE,8BAA8B;gBACtC,SAAS;gBACT,gBAAgB;aACjB,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,qBAAqB,CAAC,YAA8B;QAC1D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEpE,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AA7MD,oDA6MC","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\n\nexport interface NodeRegistrationInfo {\n nodeId: string;\n publicIp?: string;\n publicIpv6?: string;\n publicPort?: number;\n capabilities: NodeCapabilities;\n}\n\nexport interface NodeCapabilities {\n solidProtocolVersion?: string;\n storageBackends?: string[];\n authMethods?: string[];\n maxBandwidth?: number;\n supportedModes?: ('direct' | 'proxy')[];\n location?: {\n country?: string;\n region?: string;\n coordinates?: { lat: number; lon: number };\n };\n}\n\nexport interface ModeDetectionResult {\n accessMode: 'direct' | 'proxy';\n reason: string;\n subdomain: string;\n connectivityTest?: {\n success: boolean;\n latency?: number;\n error?: string;\n };\n}\n\nexport interface EdgeNodeModeDetectorOptions {\n baseDomain: string;\n connectivityTimeoutMs?: number;\n maxDirectModeAttempts?: number;\n}\n\nexport class EdgeNodeModeDetector {\n private readonly logger = getLoggerFor(this);\n private readonly baseDomain: string;\n private readonly connectivityTimeoutMs: number;\n private readonly maxDirectModeAttempts: number;\n\n public constructor(options: EdgeNodeModeDetectorOptions) {\n this.baseDomain = options.baseDomain;\n this.connectivityTimeoutMs = options.connectivityTimeoutMs ?? 3000;\n this.maxDirectModeAttempts = Math.max(1, options.maxDirectModeAttempts ?? 1);\n }\n\n public async detectMode(nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult> {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n const supportsDirect = supportedModes.has('direct');\n const supportsProxy = supportedModes.has('proxy');\n\n // Prefer direct if supported and has public IP (IPv6 or IPv4)\n const hasPublicIp = Boolean(nodeInfo.publicIp || nodeInfo.publicIpv6);\n let connectivityTest: { success: boolean; latencyMs?: number; error?: string } | undefined;\n\n if (supportsDirect && hasPublicIp) {\n // 优先测试 IPv6 连通性\n const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.publicIp!;\n const port = nodeInfo.publicPort ?? 443;\n \n connectivityTest = await this.testDirectConnectivity(ipToTest, port);\n\n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable at ${ipToTest}:${port}`);\n return {\n accessMode: 'direct',\n reason: `Direct connectivity test passed (${nodeInfo.publicIpv6 ? 'IPv6' : 'IPv4'})`,\n subdomain,\n connectivityTest,\n };\n }\n \n // 如果 IPv6 测试失败且有 IPv4,尝试 IPv4\n if (nodeInfo.publicIpv6 && nodeInfo.publicIp) {\n this.logger.debug(`IPv6 connectivity failed for ${nodeInfo.nodeId}, trying IPv4...`);\n connectivityTest = await this.testDirectConnectivity(nodeInfo.publicIp, port);\n \n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.publicIp}:${port}`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity test passed (IPv4 fallback)',\n subdomain,\n connectivityTest,\n };\n }\n }\n\n this.logger.info(`Node ${nodeInfo.nodeId} is not directly reachable, will fall back to proxy if available: ${connectivityTest.error}`);\n if (!supportsProxy) {\n return {\n accessMode: 'direct',\n reason: `Direct connectivity failed and proxy not supported: ${connectivityTest.error}`,\n subdomain,\n connectivityTest,\n };\n }\n }\n\n // Direct not available or failed; if proxy supported, use proxy mode\n if (supportsProxy) {\n return {\n accessMode: 'proxy',\n reason: supportsDirect && hasPublicIp ? 'Direct connectivity failed, using proxy' : 'Direct not available, using proxy',\n subdomain,\n connectivityTest,\n };\n }\n\n // Neither direct nor proxy viable\n return {\n accessMode: supportsDirect ? 'direct' : 'proxy',\n reason: supportsDirect ? 'Direct mode only; no proxy configured' : 'Proxy only; no direct available',\n subdomain,\n };\n }\n\n private generateSubdomain(nodeId: string): string {\n // Generate a unique subdomain based on node ID\n // For now, use a simple approach - in production you might want more sophisticated logic\n const sanitized = nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();\n return `${sanitized}.${this.baseDomain}`;\n }\n\n private async testDirectConnectivity(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n let lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success || this.maxDirectModeAttempts === 1) {\n return lastResult;\n }\n\n for (let attempt = 2; attempt <= this.maxDirectModeAttempts; attempt++) {\n lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success) {\n return lastResult;\n }\n }\n return lastResult;\n }\n\n private async singleConnectivityAttempt(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n const net = await import('node:net');\n\n return new Promise((resolve) => {\n const startTime = Date.now();\n const socket = new net.Socket();\n\n const cleanup = (): void => {\n socket.destroy();\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n resolve({\n success: false,\n error: `Connection timeout after ${this.connectivityTimeoutMs}ms`,\n });\n }, this.connectivityTimeoutMs);\n\n socket.on('connect', () => {\n clearTimeout(timeout);\n const latency = Date.now() - startTime;\n cleanup();\n resolve({\n success: true,\n latency,\n });\n });\n\n socket.on('error', (error) => {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error.message,\n });\n });\n\n try {\n socket.connect(port, ip);\n } catch (error) {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error instanceof Error ? error.message : 'Unknown connection error',\n });\n }\n });\n }\n\n public async recheckMode(currentMode: string, nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult | null> {\n // Only recheck if currently in proxy mode and public IP is available\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n if (currentMode !== 'proxy' || !nodeInfo.publicIp || !supportedModes.has('direct')) {\n return null;\n }\n\n const connectivityTest = await this.testDirectConnectivity(\n nodeInfo.publicIp,\n nodeInfo.publicPort ?? 443\n );\n\n if (connectivityTest.success) {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n this.logger.info(`Node ${nodeInfo.nodeId} connectivity restored, switching to direct mode`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity restored',\n subdomain,\n connectivityTest,\n };\n }\n\n return null;\n }\n\n private extractSupportedModes(capabilities: NodeCapabilities): Set<string> {\n const modes = new Set<string>();\n const rawModes = capabilities.supportedModes ?? ['direct', 'proxy'];\n \n for (const mode of rawModes) {\n modes.add(mode);\n }\n \n if (modes.size === 0) {\n modes.add('direct');\n modes.add('proxy');\n }\n return modes;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeModeDetector.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeModeDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\n\nexport interface NodeRegistrationInfo {\n nodeId: string;\n ipv4?: string;\n publicIpv6?: string;\n publicPort?: number;\n capabilities: NodeCapabilities;\n}\n\nexport interface NodeCapabilities {\n solidProtocolVersion?: string;\n storageBackends?: string[];\n authMethods?: string[];\n maxBandwidth?: number;\n supportedModes?: ('direct' | 'proxy')[];\n location?: {\n country?: string;\n region?: string;\n coordinates?: { lat: number; lon: number };\n };\n}\n\nexport interface ModeDetectionResult {\n accessMode: 'direct' | 'proxy';\n reason: string;\n subdomain: string;\n connectivityTest?: {\n success: boolean;\n latency?: number;\n error?: string;\n };\n}\n\nexport interface EdgeNodeModeDetectorOptions {\n baseDomain: string;\n connectivityTimeoutMs?: number;\n maxDirectModeAttempts?: number;\n}\n\nexport class EdgeNodeModeDetector {\n private readonly logger = getLoggerFor(this);\n private readonly baseDomain: string;\n private readonly connectivityTimeoutMs: number;\n private readonly maxDirectModeAttempts: number;\n\n public constructor(options: EdgeNodeModeDetectorOptions) {\n this.baseDomain = options.baseDomain;\n this.connectivityTimeoutMs = options.connectivityTimeoutMs ?? 3000;\n this.maxDirectModeAttempts = Math.max(1, options.maxDirectModeAttempts ?? 1);\n }\n\n public async detectMode(nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult> {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n const supportsDirect = supportedModes.has('direct');\n const supportsProxy = supportedModes.has('proxy');\n\n // Prefer direct if supported and has public IP (IPv6 or IPv4)\n const hasPublicIp = Boolean(nodeInfo.ipv4 || nodeInfo.publicIpv6);\n let connectivityTest: { success: boolean; latencyMs?: number; error?: string } | undefined;\n\n if (supportsDirect && hasPublicIp) {\n // 优先测试 IPv6 连通性\n const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.ipv4!;\n const port = nodeInfo.publicPort ?? 443;\n\n connectivityTest = await this.testDirectConnectivity(ipToTest, port);\n\n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable at ${ipToTest}:${port}`);\n return {\n accessMode: 'direct',\n reason: `Direct connectivity test passed (${nodeInfo.publicIpv6 ? 'IPv6' : 'IPv4'})`,\n subdomain,\n connectivityTest,\n };\n }\n\n // 如果 IPv6 测试失败且有 IPv4,尝试 IPv4\n if (nodeInfo.publicIpv6 && nodeInfo.ipv4) {\n this.logger.debug(`IPv6 connectivity failed for ${nodeInfo.nodeId}, trying IPv4...`);\n connectivityTest = await this.testDirectConnectivity(nodeInfo.ipv4, port);\n\n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.ipv4}:${port}`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity test passed (IPv4 fallback)',\n subdomain,\n connectivityTest,\n };\n }\n }\n\n this.logger.info(`Node ${nodeInfo.nodeId} is not directly reachable, will fall back to proxy if available: ${connectivityTest.error}`);\n if (!supportsProxy) {\n return {\n accessMode: 'direct',\n reason: `Direct connectivity failed and proxy not supported: ${connectivityTest.error}`,\n subdomain,\n connectivityTest,\n };\n }\n }\n\n // Direct not available or failed; if proxy supported, use proxy mode\n if (supportsProxy) {\n return {\n accessMode: 'proxy',\n reason: supportsDirect && hasPublicIp ? 'Direct connectivity failed, using proxy' : 'Direct not available, using proxy',\n subdomain,\n connectivityTest,\n };\n }\n\n // Neither direct nor proxy viable\n return {\n accessMode: supportsDirect ? 'direct' : 'proxy',\n reason: supportsDirect ? 'Direct mode only; no proxy configured' : 'Proxy only; no direct available',\n subdomain,\n };\n }\n\n private generateSubdomain(nodeId: string): string {\n // 只返回前缀,完整 FQDN 由 DnsCoordinator 的 rootDomain 拼接\n return nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();\n }\n\n private async testDirectConnectivity(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n let lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success || this.maxDirectModeAttempts === 1) {\n return lastResult;\n }\n\n for (let attempt = 2; attempt <= this.maxDirectModeAttempts; attempt++) {\n lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success) {\n return lastResult;\n }\n }\n return lastResult;\n }\n\n private async singleConnectivityAttempt(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n const net = await import('node:net');\n\n return new Promise((resolve) => {\n const startTime = Date.now();\n const socket = new net.Socket();\n\n const cleanup = (): void => {\n socket.destroy();\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n resolve({\n success: false,\n error: `Connection timeout after ${this.connectivityTimeoutMs}ms`,\n });\n }, this.connectivityTimeoutMs);\n\n socket.on('connect', () => {\n clearTimeout(timeout);\n const latency = Date.now() - startTime;\n cleanup();\n resolve({\n success: true,\n latency,\n });\n });\n\n socket.on('error', (error) => {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error.message,\n });\n });\n\n try {\n socket.connect(port, ip);\n } catch (error) {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error instanceof Error ? error.message : 'Unknown connection error',\n });\n }\n });\n }\n\n public async recheckMode(currentMode: string, nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult | null> {\n // Only recheck if currently in proxy mode and public IP is available\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n if (currentMode !== 'proxy' || !nodeInfo.ipv4 || !supportedModes.has('direct')) {\n return null;\n }\n\n const connectivityTest = await this.testDirectConnectivity(\n nodeInfo.ipv4,\n nodeInfo.publicPort ?? 443\n );\n\n if (connectivityTest.success) {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n this.logger.info(`Node ${nodeInfo.nodeId} connectivity restored, switching to direct mode`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity restored',\n subdomain,\n connectivityTest,\n };\n }\n\n return null;\n }\n\n private extractSupportedModes(capabilities: NodeCapabilities): Set<string> {\n const modes = new Set<string>();\n const rawModes = capabilities.supportedModes ?? ['direct', 'proxy'];\n \n for (const mode of rawModes) {\n modes.add(mode);\n }\n \n if (modes.size === 0) {\n modes.add('direct');\n modes.add('proxy');\n }\n return modes;\n }\n}\n"]}
|
|
@@ -106,7 +106,7 @@ class ClusterIngressRouter extends community_server_1.HttpHandler {
|
|
|
106
106
|
throw new community_server_2.InternalServerError(`Node ${nodeId} connectivity info not found.`);
|
|
107
107
|
}
|
|
108
108
|
const mode = this.normalizeMode(nodeInfo.accessMode);
|
|
109
|
-
if (mode === 'direct' && nodeInfo.
|
|
109
|
+
if (mode === 'direct' && nodeInfo.ipv4) {
|
|
110
110
|
await this.handleDirectModeRedirect(response, nodeInfo, url);
|
|
111
111
|
}
|
|
112
112
|
else if (mode === 'proxy') {
|
|
@@ -128,13 +128,13 @@ class ClusterIngressRouter extends community_server_1.HttpHandler {
|
|
|
128
128
|
*/
|
|
129
129
|
async handleDirectModeRedirect(response, nodeInfo, url) {
|
|
130
130
|
const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';
|
|
131
|
-
const nodeDirectUrl = `https://${nodeInfo.
|
|
131
|
+
const nodeDirectUrl = `https://${nodeInfo.ipv4}${port}${url.pathname}${url.search}${url.hash}`;
|
|
132
132
|
this.logger.debug(`Redirecting to edge node (direct mode): ${nodeDirectUrl}`);
|
|
133
133
|
response.statusCode = 307;
|
|
134
134
|
response.setHeader('Location', nodeDirectUrl);
|
|
135
135
|
response.setHeader('Cache-Control', 'no-cache');
|
|
136
136
|
response.setHeader('X-Xpod-Direct-Node', nodeInfo.nodeId);
|
|
137
|
-
response.setHeader('X-Xpod-Target-IP', nodeInfo.
|
|
137
|
+
response.setHeader('X-Xpod-Target-IP', nodeInfo.ipv4);
|
|
138
138
|
response.end();
|
|
139
139
|
}
|
|
140
140
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClusterIngressRouter.js","sourceRoot":"","sources":["../../src/http/ClusterIngressRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { Readable } from 'node:stream';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface ClusterIngressRouterOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n clusterIngressDomain: string; // cluster.example.com\n skipAuthRedirect?: boolean; // For testing\n fetchImpl?: any;\n}\n\n/**\n * Cluster Ingress Router - 集群统一入口路由器\n * \n * 实现我们设计的混合路由策略:\n * 1. 所有节点子域名DNS都指向集群入口\n * 2. 认证请求路由到集群IDP \n * 3. 数据请求根据节点模式智能路由(307重定向 vs 代理)\n * \n * Note: WebSocket 代理由 ClusterWebSocketConfigurator 处理\n */\nexport class ClusterIngressRouter extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly repository: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly clusterIngressDomain: string;\n private readonly skipAuthRedirect: boolean;\n private readonly fetchImpl: any;\n\n // Authentication paths that should always route to cluster IDP\n private readonly authPaths = [\n '/idp/',\n '/.well-known/openid-configuration',\n '/.well-known/oauth-authorization-server', \n '/login',\n '/logout'\n ];\n\n public constructor(options: ClusterIngressRouterOptions) {\n super();\n this.repository = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.clusterIngressDomain = this.normalizeDomain(options.clusterIngressDomain);\n this.skipAuthRedirect = options.skipAuthRedirect ?? false;\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n this.logger.debug('ClusterIngressRouter.canHandle called');\n if (!this.enabled) {\n this.logger.debug('ClusterIngressRouter disabled');\n throw new NotImplementedHttpError('Cluster ingress router disabled.');\n }\n\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new NotImplementedHttpError('Missing Host header.');\n }\n\n // Only handle requests to node subdomains, not the cluster domain itself\n if (hostname === this.clusterIngressDomain) {\n throw new NotImplementedHttpError('Request to cluster domain, not a node subdomain.');\n }\n\n // Check if this is a valid node subdomain\n const nodeId = this.extractNodeIdFromHostname(hostname);\n if (!nodeId) {\n throw new NotImplementedHttpError('Not a node subdomain pattern.');\n }\n\n // Verify the node exists\n const nodeSecret = await this.repository.getNodeSecret(nodeId);\n if (!nodeSecret) {\n throw new NotImplementedHttpError(`Node ${nodeId} not registered.`);\n }\n\n const url = this.parseUrl(request);\n if (this.isAuthenticationRequest(url.pathname)) {\n this.rewriteRequestForClusterIdp(request, hostname);\n throw new NotImplementedHttpError('Authentication routed to cluster IDP.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const hostname = this.extractHostname(request)!;\n const url = this.parseUrl(request);\n const nodeId = this.extractNodeIdFromHostname(hostname)!;\n \n // Check if this is an authentication request\n if (this.isAuthenticationRequest(url.pathname)) {\n await this.handleAuthenticationRequest(request, response, url);\n return;\n }\n\n // Data request - route based on node access mode\n await this.handleDataRequest(request, response, nodeId, url);\n }\n\n /**\n * Handle authentication requests - always route to cluster IDP\n */\n private async handleAuthenticationRequest(\n request: IncomingMessage, \n response: HttpResponse, \n url: URL\n ): Promise<void> {\n if (this.skipAuthRedirect) {\n throw new NotImplementedHttpError('Auth routing skipped for testing.');\n }\n\n // Redirect authentication requests to cluster IDP\n const clusterAuthUrl = new URL(url.pathname + url.search + url.hash, `https://${this.clusterIngressDomain}`);\n \n this.logger.debug(`Routing auth request to cluster IDP: ${clusterAuthUrl.toString()}`);\n \n response.statusCode = 307;\n response.setHeader('Location', clusterAuthUrl.toString());\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('X-Xpod-Auth-Redirect', 'cluster-idp');\n response.end();\n }\n\n /**\n * Handle data requests - route based on node access mode\n */\n private async handleDataRequest(\n request: IncomingMessage,\n response: HttpResponse, \n nodeId: string,\n url: URL\n ): Promise<void> {\n try {\n const [nodeInfo, nodeMetadata] = await Promise.all([\n this.repository.getNodeConnectivityInfo(nodeId),\n this.repository.getNodeMetadata(nodeId),\n ]);\n \n if (!nodeInfo) {\n throw new InternalServerError(`Node ${nodeId} connectivity info not found.`);\n }\n\n const mode = this.normalizeMode(nodeInfo.accessMode);\n if (mode === 'direct' && nodeInfo.publicIp) {\n await this.handleDirectModeRedirect(response, nodeInfo, url);\n } else if (mode === 'proxy') {\n await this.handleProxyModeRequest(request, response, nodeId, nodeInfo, nodeMetadata?.metadata || null, url);\n } else {\n throw new InternalServerError(`Node ${nodeId} has unsupported accessMode ${nodeInfo.accessMode ?? 'unknown'}.`);\n }\n } catch (error: unknown) {\n if (error instanceof NotImplementedHttpError) {\n throw error;\n }\n throw new InternalServerError('Failed to route data request.', { cause: error });\n }\n }\n\n /**\n * Handle direct mode - redirect to node's public IP\n */\n private async handleDirectModeRedirect(\n response: HttpResponse,\n nodeInfo: NonNullable<Awaited<ReturnType<EdgeNodeRepository['getNodeConnectivityInfo']>>>,\n url: URL\n ): Promise<void> {\n const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';\n const nodeDirectUrl = `https://${nodeInfo.publicIp}${port}${url.pathname}${url.search}${url.hash}`;\n \n this.logger.debug(`Redirecting to edge node (direct mode): ${nodeDirectUrl}`);\n \n response.statusCode = 307;\n response.setHeader('Location', nodeDirectUrl);\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('X-Xpod-Direct-Node', nodeInfo.nodeId);\n response.setHeader('X-Xpod-Target-IP', nodeInfo.publicIp!);\n response.end();\n }\n\n /**\n * Handle proxy mode - proxy the request through tunnel\n */\n private async handleProxyModeRequest(\n request: IncomingMessage,\n response: HttpResponse,\n nodeId: string,\n nodeInfo: NonNullable<Awaited<ReturnType<EdgeNodeRepository['getNodeConnectivityInfo']>>>,\n metadata: Record<string, unknown> | null,\n url: URL\n ): Promise<void> {\n // Get tunnel entrypoint from node metadata\n const upstream = this.resolveUpstream(metadata);\n if (!upstream) {\n throw new InternalServerError(`Node ${nodeId} tunnel endpoint not ready.`);\n }\n\n const upstreamBase = new URL(upstream);\n const target = new URL(url.pathname + url.search, upstreamBase);\n target.hash = url.hash;\n\n const body = await this.readRequestBody(request);\n const headers = this.buildProxyHeaders(request, url, upstreamBase);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await this.fetchImpl(target.toString(), {\n method: (request.method ?? 'GET').toUpperCase(),\n headers,\n body: body?.length ? body : undefined,\n });\n } catch (error: unknown) {\n this.logger.error(`Proxy request to ${target.toString()} failed: ${String(error)}`);\n throw new InternalServerError('Failed to proxy request to edge node.', { cause: error });\n }\n\n // Forward response\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Proxy-Node', nodeId);\n \n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return; // Let Node.js handle chunking\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n\n const readable = Readable.from(upstreamResponse.body as any);\n readable.on('error', (error) => {\n this.logger.error(`Proxy stream error: ${String(error)}`);\n response.destroy(error);\n });\n readable.pipe(response);\n }\n\n /**\n * Resolve upstream endpoint from node metadata\n */\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n \n // Fallback to publicAddress if available\n if (typeof metadata?.publicAddress === 'string') {\n return metadata.publicAddress;\n }\n \n return undefined;\n }\n\n /**\n * Build headers for proxy request\n */\n private buildProxyHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n \n // Forward original headers except host\n for (const [name, value] of Object.entries(request.headers)) {\n if (value === undefined || name.toLowerCase() === 'host') {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(name, value.join(','));\n } else {\n headers.set(name, value);\n }\n }\n \n // Set proper target host\n headers.set('host', upstream.host);\n \n // Add forwarded headers for transparency\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n \n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n \n // Add client IP if available\n const remoteAddress = (request.socket as any)?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n \n return headers;\n }\n\n /**\n * Read request body for proxy forwarding\n */\n private readRequestBody(request: IncomingMessage): Promise<Buffer | undefined> {\n const method = (request.method ?? 'GET').toUpperCase();\n if (['GET', 'HEAD'].includes(method)) {\n return Promise.resolve(undefined);\n }\n \n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => resolve(Buffer.concat(chunks)));\n request.on('error', reject);\n });\n }\n\n /**\n * Check if request path is for authentication\n */\n private isAuthenticationRequest(pathname: string): boolean {\n return this.authPaths.some(authPath => pathname.startsWith(authPath));\n }\n\n /**\n * Rewrite the incoming request so downstream handlers treat it as cluster IDP traffic.\n */\n private rewriteRequestForClusterIdp(request: IncomingMessage, originalHost: string): void {\n if (!request.headers['x-original-host']) {\n request.headers['x-original-host'] = originalHost;\n }\n\n request.headers.host = this.clusterIngressDomain;\n request.headers.Host = this.clusterIngressDomain;\n\n if (!request.headers['x-forwarded-host']) {\n request.headers['x-forwarded-host'] = originalHost;\n }\n }\n\n /**\n * Extract node ID from hostname\n * e.g., \"node1.cluster.example.com\" -> \"node1\"\n */\n private extractNodeIdFromHostname(hostname: string): string | undefined {\n const clusterSuffix = `.${this.clusterIngressDomain}`;\n if (!hostname.endsWith(clusterSuffix)) {\n return undefined;\n }\n \n const nodeId = hostname.slice(0, -clusterSuffix.length);\n // Validate node ID format (simple validation)\n if (!nodeId || nodeId.includes('.') || nodeId.length === 0) {\n return undefined;\n }\n \n return nodeId;\n }\n\n /**\n * Extract hostname from request headers\n * Check for original host header first (set by ClusterHttpServerFactory)\n */\n private extractHostname(request: IncomingMessage): string | undefined {\n // Check for original host header first (set by ClusterHttpServerFactory)\n const originalHost = request.headers['x-original-host'];\n if (originalHost && typeof originalHost === 'string') {\n return originalHost.toLowerCase();\n }\n \n const hostHeader = request.headers.host || request.headers.Host;\n if (Array.isArray(hostHeader)) {\n return hostHeader[0]?.toLowerCase();\n }\n return typeof hostHeader === 'string' ? hostHeader.toLowerCase() : undefined;\n }\n\n /**\n * Parse request URL\n */\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'https' : 'https';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n /**\n * Normalize domain input; accept bare host or full URL.\n */\n private normalizeDomain(domain: string): string {\n if (domain.includes('://')) {\n try {\n return new URL(domain).hostname.toLowerCase();\n } catch {\n return domain.toLowerCase();\n }\n }\n return domain.toLowerCase();\n }\n\n /**\n * Normalize boolean values from string/boolean\n */\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n private normalizeMode(mode: string | undefined): 'direct' | 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n // Backward compatibility for 'redirect' -> 'direct'\n if (normalized === 'redirect' || normalized === 'direct') {\n return 'direct';\n }\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ClusterIngressRouter.js","sourceRoot":"","sources":["../../src/http/ClusterIngressRouter.ts"],"names":[],"mappings":";;;AACA,iEAAqD;AACrD,6CAAuC;AACvC,8DAAsD;AAEtD,8DAIiC;AACjC,+CAA6D;AAC7D,+EAA4E;AAW5E;;;;;;;;;GASG;AACH,MAAa,oBAAqB,SAAQ,8BAAW;IAiBnD,YAAmB,OAAoC;QACrD,KAAK,EAAE,CAAC;QAjBS,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAO/C,+DAA+D;QAC9C,cAAS,GAAG;YAC3B,OAAO;YACP,mCAAmC;YACnC,yCAAyC;YACzC,QAAQ;YACR,SAAS;SACV,CAAC;QAIA,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,uCAAkB,CAAC,IAAA,wBAAmB,EAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;QAC3G,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC/D,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAC/E,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,KAAK,CAAC;QAC1D,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC9C,CAAC;IAEe,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,EAAoB;QAC3D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACnD,MAAM,IAAI,0CAAuB,CAAC,kCAAkC,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,0CAAuB,CAAC,sBAAsB,CAAC,CAAC;QAC5D,CAAC;QAED,yEAAyE;QACzE,IAAI,QAAQ,KAAK,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC3C,MAAM,IAAI,0CAAuB,CAAC,kDAAkD,CAAC,CAAC;QACxF,CAAC;QAED,0CAA0C;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,0CAAuB,CAAC,+BAA+B,CAAC,CAAC;QACrE,CAAC;QAED,yBAAyB;QACzB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,0CAAuB,CAAC,QAAQ,MAAM,kBAAkB,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,2BAA2B,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACpD,MAAM,IAAI,0CAAuB,CAAC,uCAAuC,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAEe,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAoB;QAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAE,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,yBAAyB,CAAC,QAAQ,CAAE,CAAC;QAEzD,6CAA6C;QAC7C,IAAI,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,CAAC,2BAA2B,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,iDAAiD;QACjD,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/D,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,2BAA2B,CACvC,OAAwB,EACxB,QAAsB,EACtB,GAAQ;QAER,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,MAAM,IAAI,0CAAuB,CAAC,mCAAmC,CAAC,CAAC;QACzE,CAAC;QAED,kDAAkD;QAClD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,WAAW,IAAI,CAAC,oBAAoB,EAAE,CAAC,CAAC;QAE7G,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,cAAc,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAEvF,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC1D,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAChD,QAAQ,CAAC,SAAS,CAAC,sBAAsB,EAAE,aAAa,CAAC,CAAC;QAC1D,QAAQ,CAAC,GAAG,EAAE,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAC7B,OAAwB,EACxB,QAAsB,EACtB,MAAc,EACd,GAAQ;QAER,IAAI,CAAC;YACH,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACjD,IAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC,MAAM,CAAC;gBAC/C,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,MAAM,CAAC;aACxC,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,sCAAmB,CAAC,QAAQ,MAAM,+BAA+B,CAAC,CAAC;YAC/E,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YACrD,IAAI,IAAI,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACvC,MAAM,IAAI,CAAC,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC/D,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9G,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,sCAAmB,CAAC,QAAQ,MAAM,+BAA+B,QAAQ,CAAC,UAAU,IAAI,SAAS,GAAG,CAAC,CAAC;YAClH,CAAC;QACH,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,YAAY,0CAAuB,EAAE,CAAC;gBAC7C,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,sCAAmB,CAAC,+BAA+B,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,wBAAwB,CACpC,QAAsB,EACtB,QAAyF,EACzF,GAAQ;QAER,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjG,MAAM,aAAa,GAAG,WAAW,QAAQ,CAAC,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAE/F,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,aAAa,EAAE,CAAC,CAAC;QAE9E,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAC9C,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAChD,QAAQ,CAAC,SAAS,CAAC,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1D,QAAQ,CAAC,SAAS,CAAC,kBAAkB,EAAE,QAAQ,CAAC,IAAK,CAAC,CAAC;QACvD,QAAQ,CAAC,GAAG,EAAE,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,sBAAsB,CAClC,OAAwB,EACxB,QAAsB,EACtB,MAAc,EACd,QAAyF,EACzF,QAAwC,EACxC,GAAQ;QAER,2CAA2C;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,sCAAmB,CAAC,QAAQ,MAAM,6BAA6B,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAChE,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QAEvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC;QAEnE,IAAI,gBAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,gBAAgB,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE;gBACzD,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE;gBAC/C,OAAO;gBACP,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;aACtC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,MAAM,CAAC,QAAQ,EAAE,YAAY,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACpF,MAAM,IAAI,sCAAmB,CAAC,uCAAuC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3F,CAAC;QAED,mBAAmB;QACnB,QAAQ,CAAC,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC;QAC9C,QAAQ,CAAC,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;QAEhD,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9C,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,mBAAmB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACrE,OAAO,CAAC,8BAA8B;YACxC,CAAC;YACD,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;YAC3B,QAAQ,CAAC,GAAG,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,sBAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAW,CAAC,CAAC;QAC7D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC1D,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACK,eAAe,CAAC,QAAyC;QAC/D,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,CAAC;QAChC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,UAAU,GAAI,MAAkC,CAAC,UAAU,CAAC;YAClE,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnE,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QAED,yCAAyC;QACzC,IAAI,OAAO,QAAQ,EAAE,aAAa,KAAK,QAAQ,EAAE,CAAC;YAChD,OAAO,QAAQ,CAAC,aAAa,CAAC;QAChC,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,OAAwB,EAAE,QAAa,EAAE,QAAa;QAC9E,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAE9B,uCAAuC;QACvC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5D,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;gBACzD,SAAS;YACX,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEnC,yCAAyC;QACzC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;QAEvE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9E,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;QAEtC,6BAA6B;QAC7B,MAAM,aAAa,GAAI,OAAO,CAAC,MAAc,EAAE,aAAa,CAAC;QAC7D,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;QAC7F,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,eAAe,CAAC,OAAwB;QAC9C,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACvD,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACxD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,uBAAuB,CAAC,QAAgB;QAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxE,CAAC;IAED;;OAEG;IACK,2BAA2B,CAAC,OAAwB,EAAE,YAAoB;QAChF,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACxC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,YAAY,CAAC;QACpD,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,oBAAoB,CAAC;QACjD,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,oBAAoB,CAAC;QAEjD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,GAAG,YAAY,CAAC;QACrD,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,yBAAyB,CAAC,QAAgB;QAChD,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;QACtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACtC,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACxD,8CAA8C;QAC9C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3D,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;OAGG;IACK,eAAe,CAAC,OAAwB;QAC9C,yEAAyE;QACzE,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACxD,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrD,OAAO,YAAY,CAAC,WAAW,EAAE,CAAC;QACpC,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QAChE,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACtC,CAAC;QACD,OAAO,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/E,CAAC;IAED;;OAEG;IACK,QAAQ,CAAC,OAAwB;QACvC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC;QAC/E,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACjG,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAC3E,MAAM,MAAM,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAClG,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC;QAClC,OAAO,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,MAAM,MAAM,UAAU,EAAE,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACK,eAAe,CAAC,MAAc;QACpC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;YAC9B,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACK,gBAAgB,CAAC,KAAwB;QAC/C,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC9C,OAAO,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,CAAC;QACpG,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,aAAa,CAAC,IAAwB;QAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,oDAAoD;QACpD,IAAI,UAAU,KAAK,UAAU,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;YACzD,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;YAC3B,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAtZD,oDAsZC","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { Readable } from 'node:stream';\nimport { HttpHandler } from '@solid/community-server';\nimport type { HttpHandlerInput, HttpResponse } from '@solid/community-server';\nimport {\n NotImplementedHttpError,\n InternalServerError,\n \n} from '@solid/community-server';\nimport { getIdentityDatabase } from '../identity/drizzle/db';\nimport { EdgeNodeRepository } from '../identity/drizzle/EdgeNodeRepository';\n\ninterface ClusterIngressRouterOptions {\n identityDbUrl: string;\n edgeNodesEnabled?: string | boolean;\n repository?: EdgeNodeRepository;\n clusterIngressDomain: string; // cluster.example.com\n skipAuthRedirect?: boolean; // For testing\n fetchImpl?: any;\n}\n\n/**\n * Cluster Ingress Router - 集群统一入口路由器\n * \n * 实现我们设计的混合路由策略:\n * 1. 所有节点子域名DNS都指向集群入口\n * 2. 认证请求路由到集群IDP \n * 3. 数据请求根据节点模式智能路由(307重定向 vs 代理)\n * \n * Note: WebSocket 代理由 ClusterWebSocketConfigurator 处理\n */\nexport class ClusterIngressRouter extends HttpHandler {\n protected readonly logger = getLoggerFor(this);\n private readonly repository: EdgeNodeRepository;\n private readonly enabled: boolean;\n private readonly clusterIngressDomain: string;\n private readonly skipAuthRedirect: boolean;\n private readonly fetchImpl: any;\n\n // Authentication paths that should always route to cluster IDP\n private readonly authPaths = [\n '/idp/',\n '/.well-known/openid-configuration',\n '/.well-known/oauth-authorization-server', \n '/login',\n '/logout'\n ];\n\n public constructor(options: ClusterIngressRouterOptions) {\n super();\n this.repository = options.repository ?? new EdgeNodeRepository(getIdentityDatabase(options.identityDbUrl));\n this.enabled = this.normalizeBoolean(options.edgeNodesEnabled);\n this.clusterIngressDomain = this.normalizeDomain(options.clusterIngressDomain);\n this.skipAuthRedirect = options.skipAuthRedirect ?? false;\n this.fetchImpl = options.fetchImpl ?? fetch;\n }\n\n public override async canHandle({ request }: HttpHandlerInput): Promise<void> {\n this.logger.debug('ClusterIngressRouter.canHandle called');\n if (!this.enabled) {\n this.logger.debug('ClusterIngressRouter disabled');\n throw new NotImplementedHttpError('Cluster ingress router disabled.');\n }\n\n const hostname = this.extractHostname(request);\n if (!hostname) {\n throw new NotImplementedHttpError('Missing Host header.');\n }\n\n // Only handle requests to node subdomains, not the cluster domain itself\n if (hostname === this.clusterIngressDomain) {\n throw new NotImplementedHttpError('Request to cluster domain, not a node subdomain.');\n }\n\n // Check if this is a valid node subdomain\n const nodeId = this.extractNodeIdFromHostname(hostname);\n if (!nodeId) {\n throw new NotImplementedHttpError('Not a node subdomain pattern.');\n }\n\n // Verify the node exists\n const nodeSecret = await this.repository.getNodeSecret(nodeId);\n if (!nodeSecret) {\n throw new NotImplementedHttpError(`Node ${nodeId} not registered.`);\n }\n\n const url = this.parseUrl(request);\n if (this.isAuthenticationRequest(url.pathname)) {\n this.rewriteRequestForClusterIdp(request, hostname);\n throw new NotImplementedHttpError('Authentication routed to cluster IDP.');\n }\n }\n\n public override async handle({ request, response }: HttpHandlerInput): Promise<void> {\n const hostname = this.extractHostname(request)!;\n const url = this.parseUrl(request);\n const nodeId = this.extractNodeIdFromHostname(hostname)!;\n \n // Check if this is an authentication request\n if (this.isAuthenticationRequest(url.pathname)) {\n await this.handleAuthenticationRequest(request, response, url);\n return;\n }\n\n // Data request - route based on node access mode\n await this.handleDataRequest(request, response, nodeId, url);\n }\n\n /**\n * Handle authentication requests - always route to cluster IDP\n */\n private async handleAuthenticationRequest(\n request: IncomingMessage, \n response: HttpResponse, \n url: URL\n ): Promise<void> {\n if (this.skipAuthRedirect) {\n throw new NotImplementedHttpError('Auth routing skipped for testing.');\n }\n\n // Redirect authentication requests to cluster IDP\n const clusterAuthUrl = new URL(url.pathname + url.search + url.hash, `https://${this.clusterIngressDomain}`);\n \n this.logger.debug(`Routing auth request to cluster IDP: ${clusterAuthUrl.toString()}`);\n \n response.statusCode = 307;\n response.setHeader('Location', clusterAuthUrl.toString());\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('X-Xpod-Auth-Redirect', 'cluster-idp');\n response.end();\n }\n\n /**\n * Handle data requests - route based on node access mode\n */\n private async handleDataRequest(\n request: IncomingMessage,\n response: HttpResponse, \n nodeId: string,\n url: URL\n ): Promise<void> {\n try {\n const [nodeInfo, nodeMetadata] = await Promise.all([\n this.repository.getNodeConnectivityInfo(nodeId),\n this.repository.getNodeMetadata(nodeId),\n ]);\n \n if (!nodeInfo) {\n throw new InternalServerError(`Node ${nodeId} connectivity info not found.`);\n }\n\n const mode = this.normalizeMode(nodeInfo.accessMode);\n if (mode === 'direct' && nodeInfo.ipv4) {\n await this.handleDirectModeRedirect(response, nodeInfo, url);\n } else if (mode === 'proxy') {\n await this.handleProxyModeRequest(request, response, nodeId, nodeInfo, nodeMetadata?.metadata || null, url);\n } else {\n throw new InternalServerError(`Node ${nodeId} has unsupported accessMode ${nodeInfo.accessMode ?? 'unknown'}.`);\n }\n } catch (error: unknown) {\n if (error instanceof NotImplementedHttpError) {\n throw error;\n }\n throw new InternalServerError('Failed to route data request.', { cause: error });\n }\n }\n\n /**\n * Handle direct mode - redirect to node's public IP\n */\n private async handleDirectModeRedirect(\n response: HttpResponse,\n nodeInfo: NonNullable<Awaited<ReturnType<EdgeNodeRepository['getNodeConnectivityInfo']>>>,\n url: URL\n ): Promise<void> {\n const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';\n const nodeDirectUrl = `https://${nodeInfo.ipv4}${port}${url.pathname}${url.search}${url.hash}`;\n \n this.logger.debug(`Redirecting to edge node (direct mode): ${nodeDirectUrl}`);\n \n response.statusCode = 307;\n response.setHeader('Location', nodeDirectUrl);\n response.setHeader('Cache-Control', 'no-cache');\n response.setHeader('X-Xpod-Direct-Node', nodeInfo.nodeId);\n response.setHeader('X-Xpod-Target-IP', nodeInfo.ipv4!);\n response.end();\n }\n\n /**\n * Handle proxy mode - proxy the request through tunnel\n */\n private async handleProxyModeRequest(\n request: IncomingMessage,\n response: HttpResponse,\n nodeId: string,\n nodeInfo: NonNullable<Awaited<ReturnType<EdgeNodeRepository['getNodeConnectivityInfo']>>>,\n metadata: Record<string, unknown> | null,\n url: URL\n ): Promise<void> {\n // Get tunnel entrypoint from node metadata\n const upstream = this.resolveUpstream(metadata);\n if (!upstream) {\n throw new InternalServerError(`Node ${nodeId} tunnel endpoint not ready.`);\n }\n\n const upstreamBase = new URL(upstream);\n const target = new URL(url.pathname + url.search, upstreamBase);\n target.hash = url.hash;\n\n const body = await this.readRequestBody(request);\n const headers = this.buildProxyHeaders(request, url, upstreamBase);\n\n let upstreamResponse: Response;\n try {\n upstreamResponse = await this.fetchImpl(target.toString(), {\n method: (request.method ?? 'GET').toUpperCase(),\n headers,\n body: body?.length ? body : undefined,\n });\n } catch (error: unknown) {\n this.logger.error(`Proxy request to ${target.toString()} failed: ${String(error)}`);\n throw new InternalServerError('Failed to proxy request to edge node.', { cause: error });\n }\n\n // Forward response\n response.statusCode = upstreamResponse.status;\n response.setHeader('X-Xpod-Proxy-Node', nodeId);\n \n upstreamResponse.headers.forEach((value, key) => {\n if (key.toLowerCase() === 'transfer-encoding' && value === 'chunked') {\n return; // Let Node.js handle chunking\n }\n response.setHeader(key, value);\n });\n\n if (!upstreamResponse.body) {\n response.end();\n return;\n }\n\n const readable = Readable.from(upstreamResponse.body as any);\n readable.on('error', (error) => {\n this.logger.error(`Proxy stream error: ${String(error)}`);\n response.destroy(error);\n });\n readable.pipe(response);\n }\n\n /**\n * Resolve upstream endpoint from node metadata\n */\n private resolveUpstream(metadata?: Record<string, unknown> | null): string | undefined {\n const tunnel = metadata?.tunnel;\n if (tunnel && typeof tunnel === 'object') {\n const entrypoint = (tunnel as Record<string, unknown>).entrypoint;\n if (typeof entrypoint === 'string' && entrypoint.trim().length > 0) {\n return entrypoint;\n }\n }\n \n // Fallback to publicAddress if available\n if (typeof metadata?.publicAddress === 'string') {\n return metadata.publicAddress;\n }\n \n return undefined;\n }\n\n /**\n * Build headers for proxy request\n */\n private buildProxyHeaders(request: IncomingMessage, original: URL, upstream: URL): Headers {\n const headers = new Headers();\n \n // Forward original headers except host\n for (const [name, value] of Object.entries(request.headers)) {\n if (value === undefined || name.toLowerCase() === 'host') {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(name, value.join(','));\n } else {\n headers.set(name, value);\n }\n }\n \n // Set proper target host\n headers.set('host', upstream.host);\n \n // Add forwarded headers for transparency\n headers.set('x-forwarded-host', original.host);\n headers.set('x-forwarded-proto', original.protocol.replace(/:$/u, ''));\n \n const port = original.port || (original.protocol === 'https:' ? '443' : '80');\n headers.set('x-forwarded-port', port);\n \n // Add client IP if available\n const remoteAddress = (request.socket as any)?.remoteAddress;\n if (remoteAddress) {\n const existing = headers.get('x-forwarded-for');\n headers.set('x-forwarded-for', existing ? `${existing}, ${remoteAddress}` : remoteAddress);\n }\n \n return headers;\n }\n\n /**\n * Read request body for proxy forwarding\n */\n private readRequestBody(request: IncomingMessage): Promise<Buffer | undefined> {\n const method = (request.method ?? 'GET').toUpperCase();\n if (['GET', 'HEAD'].includes(method)) {\n return Promise.resolve(undefined);\n }\n \n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n request.on('data', (chunk: Buffer) => chunks.push(chunk));\n request.on('end', () => resolve(Buffer.concat(chunks)));\n request.on('error', reject);\n });\n }\n\n /**\n * Check if request path is for authentication\n */\n private isAuthenticationRequest(pathname: string): boolean {\n return this.authPaths.some(authPath => pathname.startsWith(authPath));\n }\n\n /**\n * Rewrite the incoming request so downstream handlers treat it as cluster IDP traffic.\n */\n private rewriteRequestForClusterIdp(request: IncomingMessage, originalHost: string): void {\n if (!request.headers['x-original-host']) {\n request.headers['x-original-host'] = originalHost;\n }\n\n request.headers.host = this.clusterIngressDomain;\n request.headers.Host = this.clusterIngressDomain;\n\n if (!request.headers['x-forwarded-host']) {\n request.headers['x-forwarded-host'] = originalHost;\n }\n }\n\n /**\n * Extract node ID from hostname\n * e.g., \"node1.cluster.example.com\" -> \"node1\"\n */\n private extractNodeIdFromHostname(hostname: string): string | undefined {\n const clusterSuffix = `.${this.clusterIngressDomain}`;\n if (!hostname.endsWith(clusterSuffix)) {\n return undefined;\n }\n \n const nodeId = hostname.slice(0, -clusterSuffix.length);\n // Validate node ID format (simple validation)\n if (!nodeId || nodeId.includes('.') || nodeId.length === 0) {\n return undefined;\n }\n \n return nodeId;\n }\n\n /**\n * Extract hostname from request headers\n * Check for original host header first (set by ClusterHttpServerFactory)\n */\n private extractHostname(request: IncomingMessage): string | undefined {\n // Check for original host header first (set by ClusterHttpServerFactory)\n const originalHost = request.headers['x-original-host'];\n if (originalHost && typeof originalHost === 'string') {\n return originalHost.toLowerCase();\n }\n \n const hostHeader = request.headers.host || request.headers.Host;\n if (Array.isArray(hostHeader)) {\n return hostHeader[0]?.toLowerCase();\n }\n return typeof hostHeader === 'string' ? hostHeader.toLowerCase() : undefined;\n }\n\n /**\n * Parse request URL\n */\n private parseUrl(request: IncomingMessage): URL {\n const hostHeader = request.headers.host ?? request.headers.Host ?? 'localhost';\n const protoHeader = request.headers['x-forwarded-proto'] ?? request.headers['X-Forwarded-Proto'];\n const protocol = Array.isArray(protoHeader) ? protoHeader[0] : protoHeader;\n const scheme = typeof protocol === 'string' ? protocol.split(',')[0]?.trim() ?? 'https' : 'https';\n const rawUrl = request.url ?? '/';\n return new URL(rawUrl, `${scheme}://${hostHeader}`);\n }\n\n /**\n * Normalize domain input; accept bare host or full URL.\n */\n private normalizeDomain(domain: string): string {\n if (domain.includes('://')) {\n try {\n return new URL(domain).hostname.toLowerCase();\n } catch {\n return domain.toLowerCase();\n }\n }\n return domain.toLowerCase();\n }\n\n /**\n * Normalize boolean values from string/boolean\n */\n private normalizeBoolean(value?: string | boolean): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';\n }\n return false;\n }\n\n private normalizeMode(mode: string | undefined): 'direct' | 'proxy' | undefined {\n if (!mode) {\n return undefined;\n }\n const normalized = mode.trim().toLowerCase();\n // Backward compatibility for 'redirect' -> 'direct'\n if (normalized === 'redirect' || normalized === 'direct') {\n return 'direct';\n }\n if (normalized === 'proxy') {\n return 'proxy';\n }\n return undefined;\n }\n}\n"]}
|
|
@@ -84,10 +84,10 @@ class ClusterWebSocketConfigurator {
|
|
|
84
84
|
return true;
|
|
85
85
|
}
|
|
86
86
|
const mode = this.normalizeMode(nodeInfo.accessMode);
|
|
87
|
-
if (mode === 'direct' && nodeInfo.
|
|
87
|
+
if (mode === 'direct' && nodeInfo.ipv4) {
|
|
88
88
|
// Direct mode: redirect client to connect directly
|
|
89
89
|
const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';
|
|
90
|
-
const directUrl = `wss://${nodeInfo.
|
|
90
|
+
const directUrl = `wss://${nodeInfo.ipv4}${port}${request.url ?? '/'}`;
|
|
91
91
|
this.logger.info(`WebSocket direct mode: redirecting to ${directUrl}`);
|
|
92
92
|
socket.write(`HTTP/1.1 307 Temporary Redirect\r\n` +
|
|
93
93
|
`Location: ${directUrl}\r\n` +
|