@undefineds.co/xpod 0.4.14 → 0.4.15
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/config/cloud.json +0 -3
- package/config/local.json +0 -3
- package/config/xpod.base.json +0 -24
- package/dist/api/ai-config/AiConfigStore.d.ts +2 -2
- package/dist/api/ai-config/AiConfigStore.js +4 -1
- package/dist/api/ai-gateway/auth/CallerPodAccess.d.ts +2 -5
- package/dist/api/ai-gateway/auth/CallerPodAccess.js +3 -42
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +2 -1
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +3 -8
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +9 -32
- package/dist/api/ai-gateway/connect/index.d.ts +3 -3
- package/dist/api/ai-gateway/connect/index.js +9 -38
- package/dist/api/ai-gateway/errors.js +2 -1
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +3 -3
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +7 -11
- package/dist/api/ai-gateway/pod/HostedPodRoute.d.ts +27 -0
- package/dist/api/ai-gateway/pod/HostedPodRoute.js +47 -0
- package/dist/api/ai-gateway/pod/OwnerPodAccess.d.ts +79 -0
- package/dist/api/ai-gateway/pod/OwnerPodAccess.js +192 -0
- package/dist/api/ai-gateway/pod/PodInterfaceKeyStore.d.ts +52 -0
- package/dist/api/ai-gateway/pod/PodInterfaceKeyStore.js +61 -0
- package/dist/api/ai-gateway/quota/ProviderQuotaAdapter.d.ts +3 -3
- package/dist/api/ai-gateway/quota/ProviderQuotaAdapter.js +7 -32
- package/dist/api/auth/ClientCredentialsAuthenticator.js +5 -28
- package/dist/api/auth/TokenEndpointRoute.d.ts +18 -0
- package/dist/api/auth/TokenEndpointRoute.js +36 -0
- package/dist/api/chatkit/pod-store.d.ts +4 -4
- package/dist/api/chatkit/pod-store.js +24 -95
- package/dist/api/container/common.js +36 -23
- package/dist/api/container/local.js +62 -29
- package/dist/api/container/routes.js +39 -7
- package/dist/api/container/types.d.ts +2 -2
- package/dist/api/handlers/AiGatewayManagementHandler.d.ts +6 -0
- package/dist/api/handlers/AiGatewayManagementHandler.js +17 -4
- package/dist/api/handlers/NetworkSettingsHandler.d.ts +7 -0
- package/dist/api/handlers/NetworkSettingsHandler.js +7 -0
- package/dist/api/handlers/PodSettingsHandler.d.ts +3 -3
- package/dist/api/handlers/PodSettingsHandler.js +3 -3
- package/dist/api/handlers/ProvisionHandler.d.ts +29 -0
- package/dist/api/handlers/ProvisionHandler.js +46 -0
- package/dist/api/handlers/ReachabilityHandler.js +3 -1
- package/dist/api/runtime.d.ts +9 -0
- package/dist/api/runtime.js +16 -3
- package/dist/cli/commands/start.d.ts +9 -2
- package/dist/cli/commands/start.js +15 -17
- package/dist/components/components.jsonld +1 -1
- package/dist/components/context.jsonld +5 -42
- package/dist/edge/EdgeNodeAgent.d.ts +10 -0
- package/dist/edge/EdgeNodeAgent.js +55 -0
- package/dist/edge/EdgeNodeAgent.jsonld +8 -0
- package/dist/edge/reachability/ReachabilitySessionService.js +4 -1
- package/dist/edge/reachability/RouteSetBuilder.d.ts +18 -1
- package/dist/edge/reachability/RouteSetBuilder.js +47 -0
- package/dist/edge/reachability/RouteSetBuilder.jsonld +29 -0
- package/dist/edge/reachability/types.d.ts +8 -2
- package/dist/http/SubgraphSparqlHttpHandler.d.ts +1 -25
- package/dist/http/SubgraphSparqlHttpHandler.js +0 -100
- package/dist/http/SubgraphSparqlHttpHandler.jsonld +0 -8
- package/dist/identity/drizzle/PodInterfaceKeyRepository.d.ts +33 -0
- package/dist/identity/drizzle/PodInterfaceKeyRepository.js +78 -0
- package/dist/identity/drizzle/db.d.ts +7 -0
- package/dist/identity/drizzle/db.js +30 -0
- package/dist/identity/drizzle/schema.pg.d.ts +9 -0
- package/dist/identity/drizzle/schema.pg.js +16 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +106 -0
- package/dist/identity/drizzle/schema.sqlite.js +16 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +3 -5
- package/dist/runtime/GatewayAdminProxyAuth.d.ts +1 -16
- package/dist/runtime/GatewayAdminProxyAuth.js +2 -71
- package/dist/runtime/Proxy.d.ts +10 -1
- package/dist/runtime/Proxy.js +95 -28
- package/dist/runtime/bootstrap.d.ts +10 -0
- package/dist/runtime/bootstrap.js +24 -0
- package/dist/runtime/host/node/NodeRuntimeHost.js +5 -3
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +2 -3
- package/dist/runtime/js-runtime.js +10 -3
- package/dist/runtime/lifecycle.js +5 -4
- package/dist/runtime/port-finder.d.ts +9 -13
- package/dist/runtime/port-finder.js +18 -37
- package/dist/tunnel/AutoTunnelProvider.d.ts +56 -0
- package/dist/tunnel/AutoTunnelProvider.js +181 -0
- package/dist/tunnel/index.d.ts +1 -0
- package/dist/tunnel/index.js +3 -1
- package/node_modules/@undefineds.co/ai-connections/dist/error-wording.js +7 -2
- package/node_modules/@undefineds.co/solid-sdk/dist/access-route.cjs +230 -0
- package/node_modules/@undefineds.co/solid-sdk/dist/access-route.d.ts +76 -0
- package/node_modules/@undefineds.co/solid-sdk/dist/access-route.js +115 -0
- package/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +89 -0
- package/node_modules/@undefineds.co/solid-sdk/dist/index.d.ts +1 -0
- package/node_modules/@undefineds.co/solid-sdk/dist/index.js +1 -0
- package/node_modules/@undefineds.co/solid-sdk/package.json +6 -1
- package/package.json +2 -2
- package/static/app/assets/inrupt-smoke.js +23 -23
- package/static/auth-callback/assets/{AiConfigContext-DCXpjzpj.js → AiConfigContext-Bx0HCAW5.js} +1 -1
- package/static/auth-callback/assets/{AiConfigPage-fe-xMIXa.js → AiConfigPage-ChQImMbm.js} +1 -1
- package/static/auth-callback/assets/{DocumentProcessingPanel-BGnkfMuU.js → DocumentProcessingPanel-D830H4SH.js} +1 -1
- package/static/auth-callback/assets/{IndexLifecyclePanel-42Jups0j.js → IndexLifecyclePanel-CnlkjMF8.js} +1 -1
- package/static/auth-callback/assets/{IndexSubjectPanel-CYyLOlQU.js → IndexSubjectPanel-CdEfg_CO.js} +1 -1
- package/static/auth-callback/assets/{ModelAssignmentsPanel-BOvUH4yr.js → ModelAssignmentsPanel-D-8A8dZx.js} +1 -1
- package/static/auth-callback/assets/{ModelsPage-DwyITxt7.js → ModelsPage-Cim7cubp.js} +3 -3
- package/static/auth-callback/assets/{NetworkPage-CG9RelJA.js → NetworkPage-CfK3x-pb.js} +2 -2
- package/static/auth-callback/assets/{PaneListHeader-yvd5ohs6.js → PaneListHeader-Bf_kEd4U.js} +1 -1
- package/static/auth-callback/assets/{SearchIndexingPanel-2_a6ezzj.js → SearchIndexingPanel-4kD4RBEQ.js} +1 -1
- package/static/auth-callback/assets/{StatusSubjectPanel-8qtVxnzC.js → StatusSubjectPanel-Bw9AiTSf.js} +1 -1
- package/static/auth-callback/assets/{StatusWorkspace-CBdRahCF.js → StatusWorkspace-C-301H-0.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsPage-BZJ6vWkg.js → SystemSettingsPage-BbWn-eG6.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsSubjectPanel-xm08UhXP.js → SystemSettingsSubjectPanel-DA4wvNSm.js} +1 -1
- package/static/auth-callback/assets/{XpodAiConnectionsPodStore-V6vgGfuw.js → XpodAiConnectionsPodStore-CohdyLoX.js} +1 -1
- package/static/auth-callback/assets/{activity-CeM7xly8.js → activity-CCsb9Oam.js} +1 -1
- package/static/auth-callback/assets/ai-connections-DexTPyfC.js +1 -0
- package/static/auth-callback/assets/{auth-callback-DTG8cSj7.js → auth-callback-Ds-c62W3.js} +5 -5
- package/static/auth-callback/assets/{badge-qJo2Zr4d.js → badge-CQm5OyzR.js} +1 -1
- package/static/auth-callback/assets/{collection-runtime-Bztsz31a.js → collection-runtime-DlAuU5TW.js} +1 -1
- package/static/auth-callback/assets/{dialog-D-h6x5x0.js → dialog-CnGzg5Ff.js} +1 -1
- package/static/auth-callback/assets/{download-C2ir4lmR.js → download-DwMONJvm.js} +1 -1
- package/static/auth-callback/assets/{external-link-THLDL4On.js → external-link-Bu5AGqCO.js} +1 -1
- package/static/auth-callback/assets/{index-BlVZb9A7.js → index-B0T-1YO8.js} +1 -1
- package/static/auth-callback/assets/{index-BRpv4yO3.js → index-C8QZf_4W.js} +1 -1
- package/static/auth-callback/assets/{index-D983R1FV.js → index-DwIlrIDG.js} +1 -1
- package/static/auth-callback/assets/{index-browser-3FvR0aaf.js → index-browser-lRsMcP_7.js} +1 -1
- package/static/auth-callback/assets/{key-round-C9U5j1Ly.js → key-round-BDMtU57P.js} +1 -1
- package/static/auth-callback/assets/{rotate-ccw-DP45cD4o.js → rotate-ccw-CoKIGCsQ.js} +1 -1
- package/static/auth-callback/assets/{skeleton-Dn4EiSCS.js → skeleton-CPoSWHDr.js} +1 -1
- package/static/auth-callback/assets/{waypoints-uqFQ3uEG.js → waypoints-BeYHxqHv.js} +1 -1
- package/static/auth-callback/assets/{workspace-layout-DLYK0Wwu.js → workspace-layout-xA5YxiK3.js} +1 -1
- package/static/auth-callback/auth-callback.html +1 -1
- package/static/dashboard/assets/{AiConfigContext-DJcMECNw.js → AiConfigContext-rWx4TyiH.js} +1 -1
- package/static/dashboard/assets/{AiConfigPage-BvE7dOTy.js → AiConfigPage-StVJpDFT.js} +1 -1
- package/static/dashboard/assets/{DocumentProcessingPanel-D9780krf.js → DocumentProcessingPanel-DJ5yul2O.js} +1 -1
- package/static/dashboard/assets/{IndexLifecyclePanel-SjsUcnbM.js → IndexLifecyclePanel-CpjtgezK.js} +1 -1
- package/static/dashboard/assets/{IndexSubjectPanel-C-ym2Tqi.js → IndexSubjectPanel-BVKcot1_.js} +1 -1
- package/static/dashboard/assets/{ModelAssignmentsPanel-BRjZ-arB.js → ModelAssignmentsPanel-BxVgWZPc.js} +1 -1
- package/static/dashboard/assets/{ModelsPage-BtTU6du3.js → ModelsPage-CECBwmeO.js} +3 -3
- package/static/dashboard/assets/{NetworkPage-CV9qpkDg.js → NetworkPage-zQgFscEW.js} +2 -2
- package/static/dashboard/assets/{PaneListHeader-KyPTTBsv.js → PaneListHeader-DIPe8pS6.js} +1 -1
- package/static/dashboard/assets/{SearchIndexingPanel-DolHGawg.js → SearchIndexingPanel-lalFJ3c8.js} +1 -1
- package/static/dashboard/assets/{StatusSubjectPanel-_YZvKiYi.js → StatusSubjectPanel-CpY_KU-G.js} +1 -1
- package/static/dashboard/assets/{StatusWorkspace-Dlbh_G40.js → StatusWorkspace-BwWCii3A.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsPage-Be7QSIoz.js → SystemSettingsPage-BKZxX-_n.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsSubjectPanel-DBp0gja7.js → SystemSettingsSubjectPanel-BZrqb2C5.js} +1 -1
- package/static/dashboard/assets/{XpodAiConnectionsPodStore-ClpSjzpP.js → XpodAiConnectionsPodStore-Bbpx9dTN.js} +1 -1
- package/static/dashboard/assets/{activity-lsNbTXJu.js → activity-BJ15TLVP.js} +1 -1
- package/static/dashboard/assets/ai-connections-GG6qkTJt.js +1 -0
- package/static/dashboard/assets/{badge-CVatKjaG.js → badge-BBX5FlaB.js} +1 -1
- package/static/dashboard/assets/{collection-runtime-Ec4UnY28.js → collection-runtime-UBbHBzdf.js} +1 -1
- package/static/dashboard/assets/{dashboard-BEzsRsvF.js → dashboard-7Y6BHyV9.js} +62 -62
- package/static/dashboard/assets/{dialog-BuyelQ4V.js → dialog-QrOJ-hB0.js} +1 -1
- package/static/dashboard/assets/{download-B4Nf678F.js → download-DK9x-VOP.js} +1 -1
- package/static/dashboard/assets/{external-link-PYv9CvKL.js → external-link-Cnkn87mJ.js} +1 -1
- package/static/dashboard/assets/{index-C9XPE-mI.js → index-B1z84it5.js} +1 -1
- package/static/dashboard/assets/{index-awr7ulur.js → index-DzjauX3N.js} +1 -1
- package/static/dashboard/assets/{index-Cx69bTNh.js → index-cIa8_efI.js} +1 -1
- package/static/dashboard/assets/{key-round-VsY80U1Z.js → key-round-BoS6UqKz.js} +1 -1
- package/static/dashboard/assets/{rotate-ccw-Cz1K4vE9.js → rotate-ccw-BeWV76i0.js} +1 -1
- package/static/dashboard/assets/{skeleton-BhNv_8tO.js → skeleton-CypcJL07.js} +1 -1
- package/static/dashboard/assets/{waypoints-DYS9XFYV.js → waypoints-zHOOqfhi.js} +1 -1
- package/static/dashboard/assets/{workspace-layout-OZJAFx5V.js → workspace-layout-DZ9JuxnM.js} +1 -1
- package/static/dashboard/dashboard.html +1 -1
- package/static/settings/assets/{AiConfigContext-B8JS3MXF.js → AiConfigContext-D3b1l_Kw.js} +1 -1
- package/static/settings/assets/{AiConfigPage-C3Zl8LUl.js → AiConfigPage-DYKw-atP.js} +1 -1
- package/static/settings/assets/{DocumentProcessingPanel-DpDZAQvf.js → DocumentProcessingPanel-DguRfsIV.js} +1 -1
- package/static/settings/assets/{IndexLifecyclePanel-Ec1XF88t.js → IndexLifecyclePanel-B85qJJA4.js} +1 -1
- package/static/settings/assets/{IndexSubjectPanel-BulT9BmO.js → IndexSubjectPanel-CsN7IxaV.js} +1 -1
- package/static/settings/assets/{ModelAssignmentsPanel-BcGSmYjb.js → ModelAssignmentsPanel-DEp9byI6.js} +1 -1
- package/static/settings/assets/{ModelsPage-CvTCKy1T.js → ModelsPage-CzTnPs_G.js} +3 -3
- package/static/settings/assets/{NetworkPage-DQF5u9Bn.js → NetworkPage-BDVnmUU0.js} +2 -2
- package/static/settings/assets/{PaneListHeader-D40hqcVl.js → PaneListHeader-Cpe39N76.js} +1 -1
- package/static/settings/assets/{SearchIndexingPanel-iCUPOx_0.js → SearchIndexingPanel-DA9w8lLL.js} +1 -1
- package/static/settings/assets/{StatusSubjectPanel-E--ZHSql.js → StatusSubjectPanel-BU0Aq0CS.js} +1 -1
- package/static/settings/assets/{StatusWorkspace-YjDAx1II.js → StatusWorkspace-D1AC1i5N.js} +1 -1
- package/static/settings/assets/{SystemSettingsPage-CDdV3mC9.js → SystemSettingsPage-ChKvOioO.js} +1 -1
- package/static/settings/assets/{SystemSettingsSubjectPanel-Br8WazDq.js → SystemSettingsSubjectPanel-DyTpOnD2.js} +1 -1
- package/static/settings/assets/{XpodAiConnectionsPodStore-CLr38P_Z.js → XpodAiConnectionsPodStore-VU5Bsz1a.js} +1 -1
- package/static/settings/assets/{activity-C5DAHHTE.js → activity-DGpLfsQB.js} +1 -1
- package/static/settings/assets/ai-connections-DexTPyfC.js +1 -0
- package/static/settings/assets/{badge-D8eIyxKt.js → badge-_hCrrWRP.js} +1 -1
- package/static/settings/assets/{collection-runtime-DLc9zil8.js → collection-runtime-E40TZ8Cl.js} +1 -1
- package/static/settings/assets/{dialog-CyuR7Va4.js → dialog-DTr2JkHn.js} +1 -1
- package/static/settings/assets/{download-c0oxZrCz.js → download-B_D21DDX.js} +1 -1
- package/static/settings/assets/{external-link-D3Z34fP_.js → external-link-BATYaM13.js} +1 -1
- package/static/settings/assets/{index-CSr3-2Cj.js → index-BAlAVi-J.js} +1 -1
- package/static/settings/assets/{index-ComidxAm.js → index-Ckh4h9jj.js} +1 -1
- package/static/settings/assets/{index-CKGbRFlc.js → index-DOiwLF4B.js} +1 -1
- package/static/settings/assets/{index-browser-CAscEbae.js → index-browser-DFFWN3Kr.js} +1 -1
- package/static/settings/assets/{key-round-DyjLmXuB.js → key-round-ZTVU_1O1.js} +1 -1
- package/static/settings/assets/{rotate-ccw-679HF_ec.js → rotate-ccw-DDdw8PTU.js} +1 -1
- package/static/settings/assets/{settings-DKknqu4C.js → settings-CyLnISOv.js} +62 -62
- package/static/settings/assets/{skeleton-CkopNkPB.js → skeleton-DEzA3hc2.js} +1 -1
- package/static/settings/assets/{waypoints-CrqfAftE.js → waypoints-BSdfNvEb.js} +1 -1
- package/static/settings/assets/{workspace-layout-UN6c4qTG.js → workspace-layout-BOQnl_3d.js} +1 -1
- package/static/settings/settings.html +1 -1
- package/dist/api/ai-gateway/pod/HostedPodDataAccess.d.ts +0 -31
- package/dist/api/ai-gateway/pod/HostedPodDataAccess.js +0 -330
- package/dist/http/InternalPodDataHttpHandler.d.ts +0 -57
- package/dist/http/InternalPodDataHttpHandler.js +0 -551
- package/dist/http/InternalPodDataHttpHandler.jsonld +0 -243
- package/static/auth-callback/assets/ai-connections-C6yaE6WQ.js +0 -1
- package/static/dashboard/assets/ai-connections-CXp5gd1J.js +0 -1
- package/static/settings/assets/ai-connections-C6yaE6WQ.js +0 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { TunnelConfig, TunnelProvider, TunnelSetupOptions, TunnelStatus } from './TunnelProvider';
|
|
2
|
+
/** One tunnel the auto provider may bring up, in the order it is worth trying. */
|
|
3
|
+
export interface AutoTunnelCandidate {
|
|
4
|
+
/** Provider or profile id, used in status and in the failure detail. */
|
|
5
|
+
id: string;
|
|
6
|
+
provider: TunnelProvider;
|
|
7
|
+
}
|
|
8
|
+
export interface AutoTunnelProviderOptions {
|
|
9
|
+
candidates: readonly AutoTunnelCandidate[];
|
|
10
|
+
/**
|
|
11
|
+
* How long one candidate gets to reach `proxy-ready` before the next one is
|
|
12
|
+
* tried. A candidate that is blocked (network policy, rejected credential)
|
|
13
|
+
* usually fails faster than this; the timeout only bounds the silent ones.
|
|
14
|
+
*/
|
|
15
|
+
readinessTimeoutMs?: number;
|
|
16
|
+
/** Poll interval while waiting for readiness. */
|
|
17
|
+
pollIntervalMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Brings up whichever tunnel actually works.
|
|
21
|
+
*
|
|
22
|
+
* An operator configures the providers they have credentials for; which of them
|
|
23
|
+
* can reach its control plane is a property of the network, not of the list. So
|
|
24
|
+
* the candidates are tried in order and the first one that publishes a proxy
|
|
25
|
+
* wins, with the losers stopped and their reasons kept for the failure report.
|
|
26
|
+
* An explicit selection never goes through here: the runtime registers that
|
|
27
|
+
* provider directly, so a closed or pinned tunnel cannot come back on its own.
|
|
28
|
+
*/
|
|
29
|
+
export declare class AutoTunnelProvider implements TunnelProvider {
|
|
30
|
+
readonly name = "auto";
|
|
31
|
+
private readonly logger;
|
|
32
|
+
private readonly candidates;
|
|
33
|
+
private readonly readinessTimeoutMs;
|
|
34
|
+
private readonly pollIntervalMs;
|
|
35
|
+
private configs;
|
|
36
|
+
private active?;
|
|
37
|
+
private started;
|
|
38
|
+
private status;
|
|
39
|
+
/** Why each candidate that did not win was dropped. */
|
|
40
|
+
private readonly attempts;
|
|
41
|
+
constructor(options: AutoTunnelProviderOptions);
|
|
42
|
+
setup(options: TunnelSetupOptions): Promise<TunnelConfig>;
|
|
43
|
+
start(config?: TunnelConfig): Promise<void>;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
getStatus(): TunnelStatus;
|
|
46
|
+
getEndpoint(): string | undefined;
|
|
47
|
+
cleanup(config: TunnelConfig): Promise<void>;
|
|
48
|
+
/** Id of the candidate currently serving, once one is ready. */
|
|
49
|
+
getActiveId(): string | undefined;
|
|
50
|
+
/** The candidates that were tried, and why the ones that lost did. */
|
|
51
|
+
getAttempts(): readonly string[];
|
|
52
|
+
private waitForReady;
|
|
53
|
+
/** Stop a candidate that lost and stop counting it as started. */
|
|
54
|
+
private dropCandidate;
|
|
55
|
+
private stopLosers;
|
|
56
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AutoTunnelProvider = void 0;
|
|
4
|
+
const global_logger_factory_1 = require("global-logger-factory");
|
|
5
|
+
const TunnelLifecycle_1 = require("./TunnelLifecycle");
|
|
6
|
+
const DEFAULT_READINESS_TIMEOUT_MS = 30_000;
|
|
7
|
+
const DEFAULT_POLL_INTERVAL_MS = 250;
|
|
8
|
+
/**
|
|
9
|
+
* Brings up whichever tunnel actually works.
|
|
10
|
+
*
|
|
11
|
+
* An operator configures the providers they have credentials for; which of them
|
|
12
|
+
* can reach its control plane is a property of the network, not of the list. So
|
|
13
|
+
* the candidates are tried in order and the first one that publishes a proxy
|
|
14
|
+
* wins, with the losers stopped and their reasons kept for the failure report.
|
|
15
|
+
* An explicit selection never goes through here: the runtime registers that
|
|
16
|
+
* provider directly, so a closed or pinned tunnel cannot come back on its own.
|
|
17
|
+
*/
|
|
18
|
+
class AutoTunnelProvider {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.name = 'auto';
|
|
21
|
+
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
22
|
+
this.configs = new Map();
|
|
23
|
+
this.started = [];
|
|
24
|
+
this.status = (0, TunnelLifecycle_1.createTunnelStatus)('stopped');
|
|
25
|
+
/** Why each candidate that did not win was dropped. */
|
|
26
|
+
this.attempts = [];
|
|
27
|
+
this.candidates = options.candidates;
|
|
28
|
+
this.readinessTimeoutMs = options.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS;
|
|
29
|
+
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
30
|
+
}
|
|
31
|
+
async setup(options) {
|
|
32
|
+
this.configs = new Map();
|
|
33
|
+
for (const candidate of this.candidates) {
|
|
34
|
+
try {
|
|
35
|
+
this.configs.set(candidate.id, await candidate.provider.setup(options));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
this.attempts.push(`${candidate.id}: ${describeError(error)}`);
|
|
39
|
+
this.logger.warn(`Tunnel candidate ${candidate.id} could not be prepared: ${describeError(error)}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// The driver round-trips this config back into `start`, which drives the
|
|
43
|
+
// candidates itself, so it only has to describe the requested origin.
|
|
44
|
+
const first = this.candidates[0];
|
|
45
|
+
const config = first ? this.configs.get(first.id) : undefined;
|
|
46
|
+
return config ?? {
|
|
47
|
+
subdomain: options.subdomain,
|
|
48
|
+
provider: 'cloudflare',
|
|
49
|
+
endpoint: '',
|
|
50
|
+
originUrl: `${options.localProtocol ?? 'http'}://127.0.0.1:${options.localPort}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async start(config) {
|
|
54
|
+
void config;
|
|
55
|
+
for (const candidate of this.candidates) {
|
|
56
|
+
const candidateConfig = this.configs.get(candidate.id);
|
|
57
|
+
if (!candidateConfig) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
this.status = (0, TunnelLifecycle_1.createTunnelStatus)('process-started', { endpoint: candidate.provider.getEndpoint() });
|
|
61
|
+
try {
|
|
62
|
+
this.started.push(candidate);
|
|
63
|
+
await candidate.provider.start(candidateConfig);
|
|
64
|
+
const ready = await this.waitForReady(candidate.provider);
|
|
65
|
+
if (ready) {
|
|
66
|
+
this.active = candidate;
|
|
67
|
+
this.status = withCandidate(candidate, candidate.provider.getStatus());
|
|
68
|
+
this.logger.info(`Tunnel ready through ${candidate.id}`);
|
|
69
|
+
await this.stopLosers(candidate);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.attempts.push(`${candidate.id}: ${describeStatus(candidate.provider.getStatus())}`);
|
|
73
|
+
this.logger.warn(`Tunnel candidate ${candidate.id} did not become ready; trying the next one`);
|
|
74
|
+
await this.dropCandidate(candidate);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.attempts.push(`${candidate.id}: ${describeError(error)}`);
|
|
78
|
+
this.logger.warn(`Tunnel candidate ${candidate.id} failed: ${describeError(error)}`);
|
|
79
|
+
await this.dropCandidate(candidate);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Nothing worked: report which candidates were tried and why, so a caller
|
|
83
|
+
// never has to guess whether the tunnel is off or merely unreachable.
|
|
84
|
+
const detail = this.attempts.length > 0
|
|
85
|
+
? this.attempts.join('; ')
|
|
86
|
+
: 'no tunnel provider is configured';
|
|
87
|
+
this.status = (0, TunnelLifecycle_1.createTunnelStatus)('failed', { error: `no-tunnel-candidate-ready: ${detail}` });
|
|
88
|
+
}
|
|
89
|
+
async stop() {
|
|
90
|
+
const targets = this.started;
|
|
91
|
+
this.started = [];
|
|
92
|
+
this.active = undefined;
|
|
93
|
+
await Promise.all(targets.map(async (candidate) => {
|
|
94
|
+
await candidate.provider.stop().catch((error) => {
|
|
95
|
+
this.logger.warn(`Stopping tunnel candidate ${candidate.id} failed: ${describeError(error)}`);
|
|
96
|
+
});
|
|
97
|
+
}));
|
|
98
|
+
this.status = (0, TunnelLifecycle_1.createTunnelStatus)('stopped');
|
|
99
|
+
}
|
|
100
|
+
getStatus() {
|
|
101
|
+
if (!this.active) {
|
|
102
|
+
return this.status;
|
|
103
|
+
}
|
|
104
|
+
const current = this.active.provider.getStatus();
|
|
105
|
+
// A tunnel that was ready and then died is still the active one, so its
|
|
106
|
+
// failure is what callers must see - never a stale "connected".
|
|
107
|
+
return withCandidate(this.active, current);
|
|
108
|
+
}
|
|
109
|
+
getEndpoint() {
|
|
110
|
+
return this.active?.provider.getEndpoint();
|
|
111
|
+
}
|
|
112
|
+
async cleanup(config) {
|
|
113
|
+
await Promise.all(this.candidates.map(async (candidate) => {
|
|
114
|
+
await candidate.provider.cleanup(config).catch((error) => {
|
|
115
|
+
this.logger.warn(`Cleaning up tunnel candidate ${candidate.id} failed: ${describeError(error)}`);
|
|
116
|
+
});
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
/** Id of the candidate currently serving, once one is ready. */
|
|
120
|
+
getActiveId() {
|
|
121
|
+
return this.active?.id;
|
|
122
|
+
}
|
|
123
|
+
/** The candidates that were tried, and why the ones that lost did. */
|
|
124
|
+
getAttempts() {
|
|
125
|
+
return this.attempts;
|
|
126
|
+
}
|
|
127
|
+
async waitForReady(provider) {
|
|
128
|
+
const deadline = Date.now() + this.readinessTimeoutMs;
|
|
129
|
+
while (Date.now() < deadline) {
|
|
130
|
+
const status = provider.getStatus();
|
|
131
|
+
if (status.stage === 'proxy-ready' || status.connected) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (status.stage === 'failed') {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
await delay(this.pollIntervalMs);
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
/** Stop a candidate that lost and stop counting it as started. */
|
|
142
|
+
async dropCandidate(candidate) {
|
|
143
|
+
await candidate.provider.stop().catch((error) => {
|
|
144
|
+
this.logger.warn(`Stopping tunnel candidate ${candidate.id} failed: ${describeError(error)}`);
|
|
145
|
+
});
|
|
146
|
+
this.started = this.started.filter((started) => started.id !== candidate.id);
|
|
147
|
+
}
|
|
148
|
+
async stopLosers(winner) {
|
|
149
|
+
await Promise.all(this.started
|
|
150
|
+
.filter((candidate) => candidate.id !== winner.id)
|
|
151
|
+
.map(async (candidate) => {
|
|
152
|
+
await candidate.provider.stop().catch(() => undefined);
|
|
153
|
+
}));
|
|
154
|
+
this.started = [winner];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.AutoTunnelProvider = AutoTunnelProvider;
|
|
158
|
+
/**
|
|
159
|
+
* The winning candidate's own status, with its error text attributed to the
|
|
160
|
+
* profile id the operator configured rather than to the provider class name.
|
|
161
|
+
*/
|
|
162
|
+
function withCandidate(candidate, status) {
|
|
163
|
+
const stage = status.stage ?? (status.connected ? 'proxy-ready' : 'process-started');
|
|
164
|
+
const error = status.error ? `${candidate.id}: ${status.error}` : undefined;
|
|
165
|
+
return (0, TunnelLifecycle_1.createTunnelStatus)(stage, {
|
|
166
|
+
...status,
|
|
167
|
+
...(error ? { error } : {}),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function describeStatus(status) {
|
|
171
|
+
return status.error ?? status.stage ?? (status.connected ? 'connected' : 'unknown');
|
|
172
|
+
}
|
|
173
|
+
function describeError(error) {
|
|
174
|
+
return error instanceof Error ? error.message : String(error);
|
|
175
|
+
}
|
|
176
|
+
function delay(ms) {
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
setTimeout(resolve, ms);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=AutoTunnelProvider.js.map
|
package/dist/tunnel/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export type { TunnelProvider, TunnelConfig, TunnelSetupOptions, TunnelStatus, }
|
|
|
2
2
|
export { CloudflareTunnelProvider, type CloudflareTunnelProviderOptions, } from './CloudflareTunnelProvider';
|
|
3
3
|
export { LocalTunnelProvider, type LocalTunnelProviderOptions, } from './LocalTunnelProvider';
|
|
4
4
|
export { NgrokTunnelProvider, type NgrokTunnelProviderOptions, } from './NgrokTunnelProvider';
|
|
5
|
+
export { AutoTunnelProvider, type AutoTunnelCandidate, type AutoTunnelProviderOptions, } from './AutoTunnelProvider';
|
|
5
6
|
export type { ActiveTunnelProvider, TunnelProfile, TunnelProfileProvider, TunnelProfileState, } from './TunnelProfiles';
|
|
6
7
|
export { resolveTunnelProfileState, selectActiveTunnelProfile, } from './TunnelProfiles';
|
package/dist/tunnel/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.selectActiveTunnelProfile = exports.resolveTunnelProfileState = exports.NgrokTunnelProvider = exports.LocalTunnelProvider = exports.CloudflareTunnelProvider = void 0;
|
|
3
|
+
exports.selectActiveTunnelProfile = exports.resolveTunnelProfileState = exports.AutoTunnelProvider = exports.NgrokTunnelProvider = exports.LocalTunnelProvider = exports.CloudflareTunnelProvider = void 0;
|
|
4
4
|
var CloudflareTunnelProvider_1 = require("./CloudflareTunnelProvider");
|
|
5
5
|
Object.defineProperty(exports, "CloudflareTunnelProvider", { enumerable: true, get: function () { return CloudflareTunnelProvider_1.CloudflareTunnelProvider; } });
|
|
6
6
|
var LocalTunnelProvider_1 = require("./LocalTunnelProvider");
|
|
7
7
|
Object.defineProperty(exports, "LocalTunnelProvider", { enumerable: true, get: function () { return LocalTunnelProvider_1.LocalTunnelProvider; } });
|
|
8
8
|
var NgrokTunnelProvider_1 = require("./NgrokTunnelProvider");
|
|
9
9
|
Object.defineProperty(exports, "NgrokTunnelProvider", { enumerable: true, get: function () { return NgrokTunnelProvider_1.NgrokTunnelProvider; } });
|
|
10
|
+
var AutoTunnelProvider_1 = require("./AutoTunnelProvider");
|
|
11
|
+
Object.defineProperty(exports, "AutoTunnelProvider", { enumerable: true, get: function () { return AutoTunnelProvider_1.AutoTunnelProvider; } });
|
|
10
12
|
var TunnelProfiles_1 = require("./TunnelProfiles");
|
|
11
13
|
Object.defineProperty(exports, "resolveTunnelProfileState", { enumerable: true, get: function () { return TunnelProfiles_1.resolveTunnelProfileState; } });
|
|
12
14
|
Object.defineProperty(exports, "selectActiveTunnelProfile", { enumerable: true, get: function () { return TunnelProfiles_1.selectActiveTunnelProfile; } });
|
|
@@ -134,8 +134,13 @@ function messageForSafeErrorCode(code, provider) {
|
|
|
134
134
|
return provider
|
|
135
135
|
? `${providerLabel(provider)} does not support this operation.`
|
|
136
136
|
: 'This AI Connection operation is not supported.';
|
|
137
|
-
case '
|
|
138
|
-
|
|
137
|
+
case 'service_access_missing':
|
|
138
|
+
case 'pod_interface_key_missing':
|
|
139
|
+
case 'pod_interface_key_rejected':
|
|
140
|
+
case 'caller_dpop_replay_unsupported':
|
|
141
|
+
// Xpod reaches the Pod with the owner's own interface key, never with a
|
|
142
|
+
// deployment identity, so the fix is always on the user's side.
|
|
143
|
+
return 'Xpod 尚未获得这个 Pod 的接口访问密钥。请在「客户端访问」中创建 API Key 并应用到客户端后重试。';
|
|
139
144
|
case 'unauthorized':
|
|
140
145
|
return 'Please sign in again to continue.';
|
|
141
146
|
case 'forbidden':
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__moduleCache.set(from, entry);
|
|
22
|
+
return entry;
|
|
23
|
+
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
29
|
+
var __export = (target, all) => {
|
|
30
|
+
for (var name in all)
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/access-route.ts
|
|
40
|
+
var exports_access_route = {};
|
|
41
|
+
__export(exports_access_route, {
|
|
42
|
+
usableRoutes: () => usableRoutes,
|
|
43
|
+
createSolidAccessRouteFetch: () => createSolidAccessRouteFetch,
|
|
44
|
+
chooseAccessRoute: () => chooseAccessRoute
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(exports_access_route);
|
|
47
|
+
|
|
48
|
+
// src/local-route-fetch.ts
|
|
49
|
+
var SOLID_LOCAL_ROUTE_CANONICAL_URL_HEADER = "x-xpod-canonical-url";
|
|
50
|
+
var SOLID_LOCAL_ROUTE_CANONICAL_ORIGIN_HEADER = "x-xpod-canonical-origin";
|
|
51
|
+
var SOLID_LOCAL_ROUTE_CANONICAL_HOST_HEADER = "x-xpod-canonical-host";
|
|
52
|
+
var SOLID_LOCAL_ROUTE_LOCAL_URL_HEADER = "x-xpod-local-route-url";
|
|
53
|
+
function resolveSolidLocalRouteUrl(input, routes) {
|
|
54
|
+
let sourceUrl;
|
|
55
|
+
try {
|
|
56
|
+
sourceUrl = input instanceof URL ? new URL(input.href) : new URL(input);
|
|
57
|
+
} catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const route = routes.map(normalizeRoute).filter((candidate) => isInsideBase(sourceUrl, candidate.canonicalBaseUrl)).sort((left, right) => right.canonicalBaseUrl.length - left.canonicalBaseUrl.length)[0];
|
|
61
|
+
return route ? replaceBase(sourceUrl, route) : undefined;
|
|
62
|
+
}
|
|
63
|
+
function createSolidLocalRouteFetch(options) {
|
|
64
|
+
return async (input, init) => {
|
|
65
|
+
const sourceUrl = requestUrl(input);
|
|
66
|
+
if (!sourceUrl) {
|
|
67
|
+
return init === undefined ? options.fetch.call(globalThis, input) : options.fetch.call(globalThis, input, init);
|
|
68
|
+
}
|
|
69
|
+
const routes = options.routes();
|
|
70
|
+
const targetUrl = resolveSolidLocalRouteUrl(sourceUrl, routes);
|
|
71
|
+
if (!targetUrl) {
|
|
72
|
+
return init === undefined ? options.fetch.call(globalThis, input) : options.fetch.call(globalThis, input, init);
|
|
73
|
+
}
|
|
74
|
+
const headers = routedHeaders(input, init, sourceUrl, targetUrl);
|
|
75
|
+
let response;
|
|
76
|
+
if (input instanceof Request) {
|
|
77
|
+
const request = new Request(new Request(targetUrl, input), { headers });
|
|
78
|
+
response = init === undefined ? await options.fetch.call(globalThis, request) : await options.fetch.call(globalThis, request, { ...init, headers });
|
|
79
|
+
} else {
|
|
80
|
+
response = await options.fetch.call(globalThis, targetUrl, { ...init, headers });
|
|
81
|
+
}
|
|
82
|
+
const canonicalResponseUrl = resolveSolidLocalRouteUrl(response.url, routes.map((route) => ({
|
|
83
|
+
canonicalBaseUrl: route.localBaseUrl,
|
|
84
|
+
localBaseUrl: route.canonicalBaseUrl
|
|
85
|
+
})));
|
|
86
|
+
return canonicalResponseUrl ? withResponseUrl(response, canonicalResponseUrl.href) : response;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function withResponseUrl(response, url) {
|
|
90
|
+
const clone = response.clone.bind(response);
|
|
91
|
+
Object.defineProperties(response, {
|
|
92
|
+
url: { value: url, configurable: true },
|
|
93
|
+
clone: { value: () => withResponseUrl(clone(), url), configurable: true }
|
|
94
|
+
});
|
|
95
|
+
return response;
|
|
96
|
+
}
|
|
97
|
+
function requestUrl(input) {
|
|
98
|
+
if (input instanceof Request)
|
|
99
|
+
return new URL(input.url);
|
|
100
|
+
try {
|
|
101
|
+
return new URL(String(input));
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function normalizeRoute(route) {
|
|
107
|
+
return {
|
|
108
|
+
canonicalBaseUrl: normalizeBaseUrl(route.canonicalBaseUrl),
|
|
109
|
+
localBaseUrl: normalizeBaseUrl(route.localBaseUrl)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function normalizeBaseUrl(value) {
|
|
113
|
+
const url = new URL(value);
|
|
114
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
115
|
+
throw new TypeError("Solid local routes must use http or https");
|
|
116
|
+
}
|
|
117
|
+
url.search = "";
|
|
118
|
+
url.hash = "";
|
|
119
|
+
return url.href.endsWith("/") ? url.href : `${url.href}/`;
|
|
120
|
+
}
|
|
121
|
+
function isInsideBase(url, base) {
|
|
122
|
+
const canonical = new URL(base);
|
|
123
|
+
return url.origin === canonical.origin && url.pathname.startsWith(canonical.pathname);
|
|
124
|
+
}
|
|
125
|
+
function replaceBase(url, route) {
|
|
126
|
+
const canonical = new URL(route.canonicalBaseUrl);
|
|
127
|
+
const local = new URL(route.localBaseUrl);
|
|
128
|
+
const relativePath = url.pathname.slice(canonical.pathname.length);
|
|
129
|
+
const target = new URL(relativePath, local);
|
|
130
|
+
target.search = url.search;
|
|
131
|
+
target.hash = url.hash;
|
|
132
|
+
return target;
|
|
133
|
+
}
|
|
134
|
+
function routedHeaders(input, init, canonicalUrl, localUrl) {
|
|
135
|
+
const request = input instanceof Request ? input : undefined;
|
|
136
|
+
const headers = new Headers(request?.headers ?? undefined);
|
|
137
|
+
new Headers(init?.headers ?? undefined).forEach((value, key) => headers.set(key, value));
|
|
138
|
+
headers.set(SOLID_LOCAL_ROUTE_CANONICAL_URL_HEADER, canonicalUrl.toString());
|
|
139
|
+
headers.set(SOLID_LOCAL_ROUTE_CANONICAL_ORIGIN_HEADER, canonicalUrl.origin);
|
|
140
|
+
headers.set(SOLID_LOCAL_ROUTE_CANONICAL_HOST_HEADER, canonicalUrl.host);
|
|
141
|
+
headers.set(SOLID_LOCAL_ROUTE_LOCAL_URL_HEADER, localUrl.toString());
|
|
142
|
+
return headers;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/access-route.ts
|
|
146
|
+
async function chooseAccessRoute(routeSet, options = {}) {
|
|
147
|
+
const candidates = usableRoutes(routeSet, options.managedClient ?? true, options.allowLocalOnlyRoutes ?? false);
|
|
148
|
+
if (candidates.length === 0) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const probe = options.probe ?? defaultProbe;
|
|
152
|
+
const timeoutMs = options.probeTimeoutMs ?? 1000;
|
|
153
|
+
const results = await Promise.all(candidates.map(async (route) => ({
|
|
154
|
+
route,
|
|
155
|
+
ok: await probeWithTimeout(route, probe, timeoutMs)
|
|
156
|
+
})));
|
|
157
|
+
return results.find((result) => result.ok)?.route ?? null;
|
|
158
|
+
}
|
|
159
|
+
function usableRoutes(routeSet, managedClient, allowLocalOnlyRoutes) {
|
|
160
|
+
return routeSet.routes.filter((route) => route.health !== "unreachable").filter((route) => managedClient || !route.requiresManagedClient).filter((route) => allowLocalOnlyRoutes || route.visibility !== "local-only").slice().sort((left, right) => left.priority - right.priority || left.id.localeCompare(right.id));
|
|
161
|
+
}
|
|
162
|
+
function createSolidAccessRouteFetch(options) {
|
|
163
|
+
const managedClient = options.managedClient ?? true;
|
|
164
|
+
let selected;
|
|
165
|
+
const selectRoute = async () => {
|
|
166
|
+
const routeSet = {
|
|
167
|
+
canonicalUrl: options.routes()[0]?.canonicalUrl ?? "",
|
|
168
|
+
routes: options.routes()
|
|
169
|
+
};
|
|
170
|
+
const candidate = await chooseAccessRoute(routeSet, {
|
|
171
|
+
managedClient,
|
|
172
|
+
...options.allowLocalOnlyRoutes === undefined ? {} : { allowLocalOnlyRoutes: options.allowLocalOnlyRoutes },
|
|
173
|
+
...options.probeTimeoutMs === undefined ? {} : { probeTimeoutMs: options.probeTimeoutMs },
|
|
174
|
+
...options.probe === undefined ? {} : { probe: options.probe }
|
|
175
|
+
});
|
|
176
|
+
selected = candidate ?? undefined;
|
|
177
|
+
return candidate;
|
|
178
|
+
};
|
|
179
|
+
const routeFor = async () => {
|
|
180
|
+
if (selected && options.routes().some((route) => route.id === selected?.id && route.targetUrl === selected?.targetUrl)) {
|
|
181
|
+
return selected;
|
|
182
|
+
}
|
|
183
|
+
return await selectRoute() ?? undefined;
|
|
184
|
+
};
|
|
185
|
+
const routedFetchFor = (route) => createSolidLocalRouteFetch({
|
|
186
|
+
fetch: options.fetch,
|
|
187
|
+
routes: () => [{
|
|
188
|
+
canonicalBaseUrl: route.canonicalUrl,
|
|
189
|
+
localBaseUrl: route.targetUrl
|
|
190
|
+
}]
|
|
191
|
+
});
|
|
192
|
+
return async (input, init) => {
|
|
193
|
+
const route = await routeFor();
|
|
194
|
+
if (!route) {
|
|
195
|
+
return init === undefined ? options.fetch.call(globalThis, input) : options.fetch.call(globalThis, input, init);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
return await routedFetchFor(route)(input, init);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
selected = undefined;
|
|
201
|
+
await options.refreshRoutes?.();
|
|
202
|
+
const retryRoute = await selectRoute();
|
|
203
|
+
if (!retryRoute || retryRoute.id === route.id) {
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
return routedFetchFor(retryRoute)(input, init);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async function probeWithTimeout(route, probe, timeoutMs) {
|
|
211
|
+
const controller = new AbortController;
|
|
212
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
213
|
+
try {
|
|
214
|
+
return await Promise.resolve(probe(route, controller.signal));
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
} finally {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function defaultProbe(route, signal) {
|
|
222
|
+
if (!route.targetUrl.startsWith("http://") && !route.targetUrl.startsWith("https://")) {
|
|
223
|
+
return route.health === "healthy";
|
|
224
|
+
}
|
|
225
|
+
const response = await fetch(new URL("/.well-known/solid", route.targetUrl), {
|
|
226
|
+
method: "HEAD",
|
|
227
|
+
signal
|
|
228
|
+
});
|
|
229
|
+
return response.ok || response.status === 401 || response.status === 403;
|
|
230
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access routes for a node whose canonical URL never changes.
|
|
3
|
+
*
|
|
4
|
+
* A Pod's canonical URL is its RDF identity (`docs/multi-channel-access.md`), and
|
|
5
|
+
* the same identity can be reached over several physical paths: the node's own
|
|
6
|
+
* loopback listener, a LAN address, a public address, a hole-punched p2p socket
|
|
7
|
+
* or the user's tunnel. Callers pick by *where they are*, so the routes carry the
|
|
8
|
+
* priority that encodes it and the health that says whether it currently works.
|
|
9
|
+
*/
|
|
10
|
+
export type AccessRouteKind = 'loopback' | 'lan' | 'public-direct' | 'p2p' | 'user-tunnel' | 'xpod-relay';
|
|
11
|
+
export type AccessRouteVisibility = 'local-only' | 'same-account' | 'authorized-client' | 'public';
|
|
12
|
+
export type AccessRouteHealth = 'unknown' | 'healthy' | 'degraded' | 'unreachable';
|
|
13
|
+
export interface AccessRoute {
|
|
14
|
+
id: string;
|
|
15
|
+
nodeId?: string;
|
|
16
|
+
/** The canonical origin this route serves. Requests keep this identity. */
|
|
17
|
+
canonicalUrl: string;
|
|
18
|
+
kind: AccessRouteKind;
|
|
19
|
+
/** Where the request is actually sent. */
|
|
20
|
+
targetUrl: string;
|
|
21
|
+
/** Lower wins. Loopback is 10, lan 20, public-direct 30, p2p 40, tunnel 50. */
|
|
22
|
+
priority: number;
|
|
23
|
+
requiresManagedClient?: boolean;
|
|
24
|
+
visibility?: AccessRouteVisibility;
|
|
25
|
+
health?: AccessRouteHealth;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface AccessRouteSet {
|
|
29
|
+
nodeId?: string;
|
|
30
|
+
canonicalUrl: string;
|
|
31
|
+
generatedAt?: string;
|
|
32
|
+
routes: readonly AccessRoute[];
|
|
33
|
+
}
|
|
34
|
+
export interface ChooseAccessRouteOptions {
|
|
35
|
+
/** A client that may use private routes (LAN, p2p, tunnel). */
|
|
36
|
+
managedClient?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* A client running on the node's own host may use `local-only` routes. It has
|
|
39
|
+
* to say so: a loopback target is meaningless anywhere else.
|
|
40
|
+
*/
|
|
41
|
+
allowLocalOnlyRoutes?: boolean;
|
|
42
|
+
probeTimeoutMs?: number;
|
|
43
|
+
probe?: (route: AccessRoute, signal: AbortSignal) => Promise<boolean> | boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pick the best route that actually answers: candidates are ordered by priority
|
|
47
|
+
* and probed in parallel, so a same-machine client lands on loopback while a
|
|
48
|
+
* remote one skips it without waiting for it to time out.
|
|
49
|
+
*/
|
|
50
|
+
export declare function chooseAccessRoute(routeSet: AccessRouteSet, options?: ChooseAccessRouteOptions): Promise<AccessRoute | null>;
|
|
51
|
+
export declare function usableRoutes(routeSet: AccessRouteSet, managedClient: boolean, allowLocalOnlyRoutes: boolean): AccessRoute[];
|
|
52
|
+
export interface CreateSolidAccessRouteFetchOptions {
|
|
53
|
+
fetch: typeof globalThis.fetch;
|
|
54
|
+
/** Current route set. Re-read on every request so a caller can refresh it. */
|
|
55
|
+
routes: () => readonly AccessRoute[];
|
|
56
|
+
/**
|
|
57
|
+
* Re-discover the routes after the selected one stops working, which is how a
|
|
58
|
+
* client keeps using the newest path it knows about.
|
|
59
|
+
*/
|
|
60
|
+
refreshRoutes?: () => Promise<readonly AccessRoute[] | void> | readonly AccessRoute[] | void;
|
|
61
|
+
managedClient?: boolean;
|
|
62
|
+
/** Set by clients that run on the node's own host (`local-only` routes). */
|
|
63
|
+
allowLocalOnlyRoutes?: boolean;
|
|
64
|
+
probeTimeoutMs?: number;
|
|
65
|
+
probe?: (route: AccessRoute, signal: AbortSignal) => Promise<boolean> | boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A fetch that keeps canonical URLs canonical while sending the request over the
|
|
69
|
+
* best route this client can use.
|
|
70
|
+
*
|
|
71
|
+
* Canonical identity is preserved end to end: the target URL is rewritten, the
|
|
72
|
+
* canonical host travels in the `x-xpod-canonical-*` headers, and the response
|
|
73
|
+
* reports its canonical URL again. When the chosen route stops answering, the
|
|
74
|
+
* routes are refreshed and the request is retried once against the new best one.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createSolidAccessRouteFetch(options: CreateSolidAccessRouteFetchOptions): typeof globalThis.fetch;
|