@undefineds.co/xpod 0.4.17 → 0.4.18
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/xpod.base.json +3 -0
- package/dist/components/context.jsonld +14 -0
- package/dist/identity/ValidatingIdentityProviderHttpHandler.d.ts +32 -1
- package/dist/identity/ValidatingIdentityProviderHttpHandler.js +53 -1
- package/dist/identity/ValidatingIdentityProviderHttpHandler.jsonld +53 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +39 -10
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +15 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +1 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.d.ts +5 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +5 -1
- package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +4 -0
- package/node_modules/@undefineds.co/ai-connections/dist/controller.js +3 -1
- package/node_modules/@undefineds.co/extension-sdk/dist/layout.d.ts +39 -0
- package/node_modules/@undefineds.co/extension-sdk/dist/layout.js +40 -0
- package/node_modules/@undefineds.co/extension-sdk/dist/react/app-layout.d.ts +13 -1
- package/node_modules/@undefineds.co/extension-sdk/dist/react/app-layout.js +11 -3
- package/node_modules/@undefineds.co/extension-sdk/dist/react/workspace-layout.d.ts +13 -4
- package/node_modules/@undefineds.co/extension-sdk/dist/react/workspace-layout.js +68 -11
- package/node_modules/@undefineds.co/shared-ui/dist/auth-surface.js +6 -6
- package/node_modules/@undefineds.co/shared-ui/dist/dialog.js +4 -4
- package/node_modules/@undefineds.co/shared-ui/dist/login.js +1 -1
- package/node_modules/@undefineds.co/shared-ui/dist/theme.css +128 -79
- package/node_modules/@undefineds.co/shared-ui/dist/toast.js +1 -1
- package/node_modules/@undefineds.co/shared-ui/dist/tooltip.js +1 -1
- package/node_modules/@undefineds.co/solid-sdk/dist/access-route.cjs +87 -9
- package/node_modules/@undefineds.co/solid-sdk/dist/access-route.d.ts +31 -3
- package/node_modules/@undefineds.co/solid-sdk/dist/access-route.js +156 -14
- package/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +86 -8
- package/package.json +2 -2
- package/static/app/assets/global.css +1 -0
- package/static/app/assets/{index-BCZVzMpj.js → index-BeAi28iA.js} +1 -1
- package/static/app/assets/inrupt-smoke.js +37 -36
- package/static/app/assets/main.js +3 -3
- package/static/app/index.html +2 -2
- package/static/app/inrupt-smoke.html +2 -1
- package/static/dashboard/assets/{AiConfigContext-BedwuLTE.js → AiConfigContext-DelSqO6L.js} +1 -1
- package/static/dashboard/assets/{AiConfigPage-hNTayf8G.js → AiConfigPage-dbg0aDvd.js} +1 -1
- package/static/dashboard/assets/DocumentProcessingPanel-0-3nlwQf.js +1 -0
- package/static/dashboard/assets/IndexLifecyclePanel-DOI3IxOD.js +1 -0
- package/static/dashboard/assets/{IndexSubjectPanel-Bv97-ouZ.js → IndexSubjectPanel-ClDgj0SY.js} +1 -1
- package/static/dashboard/assets/ModelAssignmentsPanel-BSWH19oT.js +1 -0
- package/static/dashboard/assets/ModelsPage-BrzxCkEl.js +11 -0
- package/static/{settings/assets/NetworkPage-D50kdbEu.js → dashboard/assets/NetworkPage-CxEKeaPz.js} +2 -2
- package/static/dashboard/assets/{PaneListHeader-ByqK_C6i.js → PaneListHeader-xEjcBu9g.js} +1 -1
- package/static/dashboard/assets/SearchIndexingPanel-BGlSvEl5.js +1 -0
- package/static/dashboard/assets/{StatusSubjectPanel-DPegcFpA.js → StatusSubjectPanel-6s3JXbOd.js} +1 -1
- package/static/dashboard/assets/StatusWorkspace-LYMDa4b0.js +1 -0
- package/static/dashboard/assets/{SystemSettingsPage-LyXmd6uP.js → SystemSettingsPage-CF_vH-qe.js} +1 -1
- package/static/dashboard/assets/SystemSettingsSubjectPanel-hsfHcJRV.js +33 -0
- package/static/dashboard/assets/{XpodAiConnectionsPodStore-DXNERI2-.js → XpodAiConnectionsPodStore-CHM-ZwI3.js} +1 -1
- package/static/dashboard/assets/{activity-BwRbcTha.js → activity-B7UdTZ1N.js} +1 -1
- package/static/dashboard/assets/{badge-BbRb9MsQ.js → badge-B6RuxNt1.js} +1 -1
- package/static/dashboard/assets/collection-runtime-CKBQm3Fl.js +1 -0
- package/static/dashboard/assets/dashboard-1zrtbMhM.css +1 -0
- package/static/dashboard/assets/{dashboard-D2ozrATv.js → dashboard-Dm_h60lA.js} +175 -135
- package/static/dashboard/assets/{download-SIHe8Y5t.js → download-BW035kpj.js} +1 -1
- package/static/dashboard/assets/{external-link-CiYw5Q4U.js → external-link-BMrNvf3c.js} +1 -1
- package/static/dashboard/assets/{index-DC1jRaVA.js → index-2jlFzexm.js} +1 -1
- package/static/dashboard/assets/index-CQDQ8WVr.js +4 -0
- package/static/dashboard/assets/{key-round-nKtxP6pb.js → key-round-C9vxhn7T.js} +1 -1
- package/static/dashboard/assets/{rotate-ccw-CQm5hHMN.js → rotate-ccw-8Vm0-INc.js} +1 -1
- package/static/dashboard/assets/{skeleton-TKyLbiPs.js → skeleton-CY_602wn.js} +1 -1
- package/static/dashboard/assets/{waypoints-BQS9t0ky.js → waypoints-DdEdgkxD.js} +1 -1
- package/static/dashboard/assets/workspace-layout-Wed840qR.js +1 -0
- package/static/dashboard/dashboard.html +2 -2
- package/static/settings/assets/{AiConfigContext-eLRkXFtH.js → AiConfigContext-CR3YhTA-.js} +1 -1
- package/static/settings/assets/AiConfigPage-DnOvX2Kx.js +1 -0
- package/static/settings/assets/DocumentProcessingPanel-CccpHBs3.js +1 -0
- package/static/settings/assets/IndexLifecyclePanel-BrECzBoM.js +1 -0
- package/static/settings/assets/{IndexSubjectPanel-DT0c11VQ.js → IndexSubjectPanel-CP84xXye.js} +1 -1
- package/static/settings/assets/ModelAssignmentsPanel-DbFi67Kk.js +1 -0
- package/static/settings/assets/ModelsPage-xjHA88qy.js +11 -0
- package/static/settings/assets/NetworkPage-Bj4wc0bh.js +2 -0
- package/static/settings/assets/{PaneListHeader-Da5jZFvG.js → PaneListHeader-Bz0UAhZr.js} +1 -1
- package/static/settings/assets/SearchIndexingPanel-ykhvw1Vl.js +1 -0
- package/static/settings/assets/{StatusSubjectPanel-Upqqlvon.js → StatusSubjectPanel-B2Y6ib3d.js} +1 -1
- package/static/settings/assets/StatusWorkspace-tx3Xg56J.js +1 -0
- package/static/settings/assets/SystemSettingsPage-BbFkv_Wn.js +1 -0
- package/static/settings/assets/SystemSettingsSubjectPanel-CcbpQmTc.js +33 -0
- package/static/settings/assets/{XpodAiConnectionsPodStore-nFgD_usD.js → XpodAiConnectionsPodStore-B11u0BOi.js} +2 -2
- package/static/settings/assets/{activity-CWy1W2W0.js → activity-BE8Hz8WF.js} +1 -1
- package/static/settings/assets/auth-callback-5waKJF0n.js +1 -0
- package/static/settings/assets/{badge-v30yFaaj.js → badge-xeRbYBMf.js} +1 -1
- package/static/settings/assets/collection-runtime-DIBObcT5.js +1 -0
- package/static/settings/assets/{download-C3Dzb60R.js → download-xI5yyfk7.js} +1 -1
- package/static/settings/assets/{external-link-DnkJGKNc.js → external-link-DmbiCMuc.js} +1 -1
- package/static/settings/assets/global-1zrtbMhM.css +1 -0
- package/static/settings/assets/{global-B_WO-aYY.js → global-BOl78fr8.js} +160 -120
- package/static/settings/assets/index-CFVXiUnV.js +4 -0
- package/static/settings/assets/index-DK6qlahe.js +1 -0
- package/static/settings/assets/{index-browser-DzhsXZHm.js → index-browser-Bk7jaLCd.js} +1 -1
- package/static/settings/assets/{key-round-DoDbsLae.js → key-round-B3VIp8oI.js} +1 -1
- package/static/settings/assets/{rotate-ccw-DNR_R3oT.js → rotate-ccw-DRzTN49Z.js} +1 -1
- package/static/settings/assets/{settings-BOznrBjy.js → settings-CCM17CMX.js} +1 -1
- package/static/settings/assets/{skeleton-BjHhu9qI.js → skeleton-Ckhw5HG7.js} +1 -1
- package/static/settings/assets/{waypoints-BXNph9Fd.js → waypoints-BFMI-qS6.js} +1 -1
- package/static/settings/assets/workspace-layout-Co0KpMKd.js +1 -0
- package/static/settings/auth-callback.html +3 -3
- package/static/settings/settings.html +3 -3
- package/static/app/assets/main.css +0 -1
- package/static/dashboard/assets/DocumentProcessingPanel-BSb_vOj7.js +0 -1
- package/static/dashboard/assets/IndexLifecyclePanel-DAHx-PBG.js +0 -1
- package/static/dashboard/assets/ModelAssignmentsPanel-CEJZs9At.js +0 -1
- package/static/dashboard/assets/ModelsPage-DkBVXmKT.js +0 -11
- package/static/dashboard/assets/NetworkPage-BDqKgRZQ.js +0 -2
- package/static/dashboard/assets/SearchIndexingPanel-M-WYaQP3.js +0 -1
- package/static/dashboard/assets/StatusWorkspace-CPIkjO9S.js +0 -1
- package/static/dashboard/assets/SystemSettingsSubjectPanel-CYHqNIn2.js +0 -33
- package/static/dashboard/assets/collection-runtime-BRfZD5oS.js +0 -1
- package/static/dashboard/assets/dashboard-rk080mvG.css +0 -1
- package/static/dashboard/assets/dialog-BbRbILBP.js +0 -1
- package/static/dashboard/assets/index-CExLoewO.js +0 -41
- package/static/dashboard/assets/index-CTgU8YW1.js +0 -4
- package/static/dashboard/assets/workspace-layout-Csbbr3vZ.js +0 -1
- package/static/settings/assets/AiConfigPage-BMO-ytdK.js +0 -1
- package/static/settings/assets/DocumentProcessingPanel-CH-HBRzQ.js +0 -1
- package/static/settings/assets/IndexLifecyclePanel-ChEMn9D8.js +0 -1
- package/static/settings/assets/ModelAssignmentsPanel-T59L_wOk.js +0 -1
- package/static/settings/assets/ModelsPage-m_SP4PdE.js +0 -11
- package/static/settings/assets/SearchIndexingPanel-DQQd3-Jw.js +0 -1
- package/static/settings/assets/StatusWorkspace-CPB49bGd.js +0 -1
- package/static/settings/assets/SystemSettingsPage-C0wrxcNw.js +0 -1
- package/static/settings/assets/SystemSettingsSubjectPanel-BOm7tUH1.js +0 -33
- package/static/settings/assets/auth-callback-HhgEuohz.js +0 -1
- package/static/settings/assets/collection-runtime-B7oomxic.js +0 -1
- package/static/settings/assets/dialog-BAe7moyL.js +0 -1
- package/static/settings/assets/global-rk080mvG.css +0 -1
- package/static/settings/assets/index-B51ot_m_.js +0 -1
- package/static/settings/assets/index-CHBQVfuZ.js +0 -41
- package/static/settings/assets/index-DbqRFton.js +0 -4
- package/static/settings/assets/workspace-layout-TZ7fteel.js +0 -1
- /package/static/app/assets/{_commonjsHelpers-B-UnjaXt.js → global-BGMlqT2c.js} +0 -0
|
@@ -64,5 +64,5 @@ function getSnapshot() {
|
|
|
64
64
|
}
|
|
65
65
|
export function Toaster({ className }) {
|
|
66
66
|
const toasts = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
67
|
-
return (_jsx("div", { "aria-live": "polite", role: "status", className: cn('pointer-events-none fixed bottom-4 right-4 z-[
|
|
67
|
+
return (_jsx("div", { "aria-live": "polite", role: "status", className: cn('pointer-events-none fixed bottom-4 right-4 z-[var(--layer-toast)] flex w-full max-w-sm flex-col gap-2', className), children: toasts.map((item) => (_jsxs("div", { className: cn(toastVariants({ variant: item.variant }), item.className), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [item.title ? _jsx("p", { className: "font-medium", children: item.title }) : null, item.description ? _jsx("p", { className: cn(item.title ? 'mt-0.5' : undefined), children: item.description }) : null] }), _jsx("button", { type: "button", "aria-label": "\u5173\u95ED\u901A\u77E5", className: cn('shrink-0 rounded-sm opacity-60 transition-opacity hover:opacity-100', interactiveFocusClass), onClick: () => dismissToast(item.id), children: _jsx(X, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })] }, item.id))) }));
|
|
68
68
|
}
|
|
@@ -5,6 +5,6 @@ import { cn } from "./utils.js";
|
|
|
5
5
|
const TooltipProvider = TooltipPrimitive.Provider;
|
|
6
6
|
const Tooltip = TooltipPrimitive.Root;
|
|
7
7
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
8
|
-
const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-
|
|
8
|
+
const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(TooltipPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-[var(--layer-popover)] overflow-hidden rounded-md border border-border/50 bg-popover", "px-2 py-1 text-xs text-popover-foreground shadow-md", "animate-in fade-in-0 zoom-in-95", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2", "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className), ...props })));
|
|
9
9
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
10
10
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -41,7 +41,8 @@ var exports_access_route = {};
|
|
|
41
41
|
__export(exports_access_route, {
|
|
42
42
|
usableRoutes: () => usableRoutes,
|
|
43
43
|
createSolidAccessRouteFetch: () => createSolidAccessRouteFetch,
|
|
44
|
-
chooseAccessRoute: () => chooseAccessRoute
|
|
44
|
+
chooseAccessRoute: () => chooseAccessRoute,
|
|
45
|
+
NoUsableAccessRouteError: () => NoUsableAccessRouteError
|
|
45
46
|
});
|
|
46
47
|
module.exports = __toCommonJS(exports_access_route);
|
|
47
48
|
|
|
@@ -148,24 +149,34 @@ async function chooseAccessRoute(routeSet, options = {}) {
|
|
|
148
149
|
if (candidates.length === 0) {
|
|
149
150
|
return null;
|
|
150
151
|
}
|
|
152
|
+
const best = candidates[0];
|
|
153
|
+
if (isSelfProvenRoute(best)) {
|
|
154
|
+
return best;
|
|
155
|
+
}
|
|
151
156
|
const probe = options.probe ?? defaultProbe;
|
|
152
157
|
const timeoutMs = options.probeTimeoutMs ?? 1000;
|
|
153
158
|
const results = await Promise.all(candidates.map(async (route) => ({
|
|
154
159
|
route,
|
|
155
|
-
ok: await probeWithTimeout(route, probe, timeoutMs)
|
|
160
|
+
ok: isSelfProvenRoute(route) || await probeWithTimeout(route, probe, timeoutMs)
|
|
156
161
|
})));
|
|
157
162
|
return results.find((result) => result.ok)?.route ?? null;
|
|
158
163
|
}
|
|
164
|
+
function isSelfProvenRoute(route) {
|
|
165
|
+
return route.health === "healthy";
|
|
166
|
+
}
|
|
159
167
|
function usableRoutes(routeSet, managedClient, allowLocalOnlyRoutes) {
|
|
160
168
|
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
169
|
}
|
|
162
170
|
function createSolidAccessRouteFetch(options) {
|
|
163
171
|
const managedClient = options.managedClient ?? true;
|
|
164
172
|
let selected;
|
|
173
|
+
let failed;
|
|
165
174
|
const selectRoute = async () => {
|
|
175
|
+
const all = options.routes();
|
|
176
|
+
const routes = failed === undefined ? all : all.filter((route) => routeKey(route) !== failed);
|
|
166
177
|
const routeSet = {
|
|
167
|
-
canonicalUrl:
|
|
168
|
-
routes
|
|
178
|
+
canonicalUrl: routes[0]?.canonicalUrl ?? "",
|
|
179
|
+
routes
|
|
169
180
|
};
|
|
170
181
|
const candidate = await chooseAccessRoute(routeSet, {
|
|
171
182
|
managedClient,
|
|
@@ -189,24 +200,88 @@ function createSolidAccessRouteFetch(options) {
|
|
|
189
200
|
localBaseUrl: route.targetUrl
|
|
190
201
|
}]
|
|
191
202
|
});
|
|
203
|
+
const routeCovers = (url) => Boolean(resolveSolidLocalRouteUrl(url, options.routes().map(toLocalRoute)));
|
|
204
|
+
const unreachablePassThrough = (input) => {
|
|
205
|
+
const url = requestUrl2(input);
|
|
206
|
+
if (!url || !routeCovers(url)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const routes = options.routes();
|
|
210
|
+
const preferred = usableRoutes({ canonicalUrl: routes[0]?.canonicalUrl ?? "", routes }, managedClient, options.allowLocalOnlyRoutes ?? false)[0];
|
|
211
|
+
if (!preferred || sameOrigin(preferred.targetUrl, url)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
return url;
|
|
215
|
+
};
|
|
192
216
|
return async (input, init) => {
|
|
193
217
|
const route = await routeFor();
|
|
194
218
|
if (!route) {
|
|
219
|
+
const unreachable = unreachablePassThrough(input);
|
|
220
|
+
if (unreachable) {
|
|
221
|
+
throw new NoUsableAccessRouteError(unreachable.href, options.routes());
|
|
222
|
+
}
|
|
195
223
|
return init === undefined ? options.fetch.call(globalThis, input) : options.fetch.call(globalThis, input, init);
|
|
196
224
|
}
|
|
197
225
|
try {
|
|
198
|
-
|
|
226
|
+
const response = await routedFetchFor(route)(input, init);
|
|
227
|
+
failed = undefined;
|
|
228
|
+
return response;
|
|
199
229
|
} catch (error) {
|
|
230
|
+
failed = routeKey(route);
|
|
200
231
|
selected = undefined;
|
|
201
232
|
await options.refreshRoutes?.();
|
|
202
233
|
const retryRoute = await selectRoute();
|
|
203
|
-
if (
|
|
204
|
-
|
|
234
|
+
if (retryRoute && routeKey(retryRoute) !== routeKey(route)) {
|
|
235
|
+
return routedFetchFor(retryRoute)(input, init);
|
|
205
236
|
}
|
|
206
|
-
|
|
237
|
+
const url = requestUrl2(input);
|
|
238
|
+
if (url && routeCovers(url)) {
|
|
239
|
+
throw new NoUsableAccessRouteError(url.href, options.routes(), error);
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
207
242
|
}
|
|
208
243
|
};
|
|
209
244
|
}
|
|
245
|
+
function routeKey(route) {
|
|
246
|
+
return `${route.id}|${route.targetUrl}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
class NoUsableAccessRouteError extends Error {
|
|
250
|
+
url;
|
|
251
|
+
routes;
|
|
252
|
+
reason;
|
|
253
|
+
constructor(url, routes, reason) {
|
|
254
|
+
super(`No usable access route can reach ${url}`);
|
|
255
|
+
this.url = url;
|
|
256
|
+
this.routes = routes;
|
|
257
|
+
this.reason = reason;
|
|
258
|
+
this.name = "NoUsableAccessRouteError";
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function toLocalRoute(route) {
|
|
262
|
+
return { canonicalBaseUrl: route.canonicalUrl, localBaseUrl: route.targetUrl };
|
|
263
|
+
}
|
|
264
|
+
function sameOrigin(value, url) {
|
|
265
|
+
try {
|
|
266
|
+
return new URL(value).origin === url.origin;
|
|
267
|
+
} catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function requestUrl2(input) {
|
|
272
|
+
if (input instanceof Request) {
|
|
273
|
+
try {
|
|
274
|
+
return new URL(input.url);
|
|
275
|
+
} catch {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
return new URL(String(input));
|
|
281
|
+
} catch {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
210
285
|
async function probeWithTimeout(route, probe, timeoutMs) {
|
|
211
286
|
const controller = new AbortController;
|
|
212
287
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -218,6 +293,9 @@ async function probeWithTimeout(route, probe, timeoutMs) {
|
|
|
218
293
|
clearTimeout(timer);
|
|
219
294
|
}
|
|
220
295
|
}
|
|
296
|
+
function answersAccessRoute(response) {
|
|
297
|
+
return response.status < 500;
|
|
298
|
+
}
|
|
221
299
|
async function defaultProbe(route, signal) {
|
|
222
300
|
if (!route.targetUrl.startsWith("http://") && !route.targetUrl.startsWith("https://")) {
|
|
223
301
|
return route.health === "healthy";
|
|
@@ -226,5 +304,5 @@ async function defaultProbe(route, signal) {
|
|
|
226
304
|
method: "HEAD",
|
|
227
305
|
signal
|
|
228
306
|
});
|
|
229
|
-
return response
|
|
307
|
+
return answersAccessRoute(response);
|
|
230
308
|
}
|
|
@@ -43,9 +43,20 @@ export interface ChooseAccessRouteOptions {
|
|
|
43
43
|
probe?: (route: AccessRoute, signal: AbortSignal) => Promise<boolean> | boolean;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Pick the best route that
|
|
47
|
-
* and
|
|
48
|
-
*
|
|
46
|
+
* Pick the best route that is known to work: candidates are ordered by priority,
|
|
47
|
+
* and only the ones this client cannot vouch for are probed.
|
|
48
|
+
*
|
|
49
|
+
* A route the client already knows is up is never probed. The current origin
|
|
50
|
+
* served this very document, and the node's own runtime reports the health of
|
|
51
|
+
* the routes beside it, so a probe could only replace a known fact with a guess.
|
|
52
|
+
* That guess is what made local login fail: the local gateway answers the probe
|
|
53
|
+
* path with 405 (CSS does not implement the optional `/.well-known/solid`), the
|
|
54
|
+
* probe called the one reachable route dead, and the client fell back to a
|
|
55
|
+
* canonical URL it could not reach.
|
|
56
|
+
*
|
|
57
|
+
* Probes remain for candidates whose health is unknown, and are run in parallel
|
|
58
|
+
* so a same-machine client still lands on loopback without waiting for a remote
|
|
59
|
+
* one to time out.
|
|
49
60
|
*/
|
|
50
61
|
export declare function chooseAccessRoute(routeSet: AccessRouteSet, options?: ChooseAccessRouteOptions): Promise<AccessRoute | null>;
|
|
51
62
|
export declare function usableRoutes(routeSet: AccessRouteSet, managedClient: boolean, allowLocalOnlyRoutes: boolean): AccessRoute[];
|
|
@@ -74,3 +85,20 @@ export interface CreateSolidAccessRouteFetchOptions {
|
|
|
74
85
|
* routes are refreshed and the request is retried once against the new best one.
|
|
75
86
|
*/
|
|
76
87
|
export declare function createSolidAccessRouteFetch(options: CreateSolidAccessRouteFetchOptions): typeof globalThis.fetch;
|
|
88
|
+
/**
|
|
89
|
+
* A URL this client can only reach through a route, with no route usable.
|
|
90
|
+
*
|
|
91
|
+
* Thrown instead of sending the request to its canonical address, because that
|
|
92
|
+
* address is not the path this client meant to take: something is wrong with the
|
|
93
|
+
* routes themselves (the node's own gateway is down, a tunnel is gone), and the
|
|
94
|
+
* caller has to say so rather than report whatever the canonical URL answers.
|
|
95
|
+
*/
|
|
96
|
+
export declare class NoUsableAccessRouteError extends Error {
|
|
97
|
+
readonly url: string;
|
|
98
|
+
readonly routes: readonly AccessRoute[];
|
|
99
|
+
/** The transport failure that left no route usable, when there was one. */
|
|
100
|
+
readonly reason?: unknown | undefined;
|
|
101
|
+
constructor(url: string, routes: readonly AccessRoute[],
|
|
102
|
+
/** The transport failure that left no route usable, when there was one. */
|
|
103
|
+
reason?: unknown | undefined);
|
|
104
|
+
}
|
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
import { createSolidLocalRouteFetch } from './local-route-fetch.js';
|
|
1
|
+
import { createSolidLocalRouteFetch, resolveSolidLocalRouteUrl } from './local-route-fetch.js';
|
|
2
2
|
/**
|
|
3
|
-
* Pick the best route that
|
|
4
|
-
* and
|
|
5
|
-
*
|
|
3
|
+
* Pick the best route that is known to work: candidates are ordered by priority,
|
|
4
|
+
* and only the ones this client cannot vouch for are probed.
|
|
5
|
+
*
|
|
6
|
+
* A route the client already knows is up is never probed. The current origin
|
|
7
|
+
* served this very document, and the node's own runtime reports the health of
|
|
8
|
+
* the routes beside it, so a probe could only replace a known fact with a guess.
|
|
9
|
+
* That guess is what made local login fail: the local gateway answers the probe
|
|
10
|
+
* path with 405 (CSS does not implement the optional `/.well-known/solid`), the
|
|
11
|
+
* probe called the one reachable route dead, and the client fell back to a
|
|
12
|
+
* canonical URL it could not reach.
|
|
13
|
+
*
|
|
14
|
+
* Probes remain for candidates whose health is unknown, and are run in parallel
|
|
15
|
+
* so a same-machine client still lands on loopback without waiting for a remote
|
|
16
|
+
* one to time out.
|
|
6
17
|
*/
|
|
7
18
|
export async function chooseAccessRoute(routeSet, options = {}) {
|
|
8
19
|
const candidates = usableRoutes(routeSet, options.managedClient ?? true, options.allowLocalOnlyRoutes ?? false);
|
|
9
20
|
if (candidates.length === 0) {
|
|
10
21
|
return null;
|
|
11
22
|
}
|
|
23
|
+
const best = candidates[0];
|
|
24
|
+
if (isSelfProvenRoute(best)) {
|
|
25
|
+
return best;
|
|
26
|
+
}
|
|
12
27
|
const probe = options.probe ?? defaultProbe;
|
|
13
28
|
const timeoutMs = options.probeTimeoutMs ?? 1000;
|
|
14
29
|
const results = await Promise.all(candidates.map(async (route) => ({
|
|
15
30
|
route,
|
|
16
|
-
ok: await probeWithTimeout(route, probe, timeoutMs),
|
|
31
|
+
ok: isSelfProvenRoute(route) || await probeWithTimeout(route, probe, timeoutMs),
|
|
17
32
|
})));
|
|
18
33
|
return results.find((result) => result.ok)?.route ?? null;
|
|
19
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* A route this client can vouch for without asking: `healthy` is the runtime's
|
|
37
|
+
* own report about its own node, and the current-origin route is the document
|
|
38
|
+
* this client is already running in. `unknown` and `degraded` still get probed.
|
|
39
|
+
*/
|
|
40
|
+
function isSelfProvenRoute(route) {
|
|
41
|
+
return route.health === 'healthy';
|
|
42
|
+
}
|
|
20
43
|
export function usableRoutes(routeSet, managedClient, allowLocalOnlyRoutes) {
|
|
21
44
|
return routeSet.routes
|
|
22
45
|
.filter((route) => route.health !== 'unreachable')
|
|
@@ -37,10 +60,16 @@ export function usableRoutes(routeSet, managedClient, allowLocalOnlyRoutes) {
|
|
|
37
60
|
export function createSolidAccessRouteFetch(options) {
|
|
38
61
|
const managedClient = options.managedClient ?? true;
|
|
39
62
|
let selected;
|
|
63
|
+
// A route that just failed the request itself is the one route we must not
|
|
64
|
+
// pick again for the retry: a trusted route that turns out to be down has to
|
|
65
|
+
// be able to fail over to a probed one. Cleared by the next success.
|
|
66
|
+
let failed;
|
|
40
67
|
const selectRoute = async () => {
|
|
68
|
+
const all = options.routes();
|
|
69
|
+
const routes = failed === undefined ? all : all.filter((route) => routeKey(route) !== failed);
|
|
41
70
|
const routeSet = {
|
|
42
|
-
canonicalUrl:
|
|
43
|
-
routes
|
|
71
|
+
canonicalUrl: routes[0]?.canonicalUrl ?? '',
|
|
72
|
+
routes,
|
|
44
73
|
};
|
|
45
74
|
const candidate = await chooseAccessRoute(routeSet, {
|
|
46
75
|
managedClient,
|
|
@@ -66,29 +95,126 @@ export function createSolidAccessRouteFetch(options) {
|
|
|
66
95
|
localBaseUrl: route.targetUrl,
|
|
67
96
|
}],
|
|
68
97
|
});
|
|
98
|
+
/**
|
|
99
|
+
* Whether this client knows a route for the URL: the URLs it may rewrite and
|
|
100
|
+
* therefore the only ones it is entitled to judge. Everything else — the IdP,
|
|
101
|
+
* another Pod — is sent unchanged, whatever the routes are doing.
|
|
102
|
+
*/
|
|
103
|
+
const routeCovers = (url) => Boolean(resolveSolidLocalRouteUrl(url, options.routes().map(toLocalRoute)));
|
|
104
|
+
/**
|
|
105
|
+
* The URL a request would have to reach if it is sent unchanged, when sending
|
|
106
|
+
* it unchanged is *not* the path this client meant to take.
|
|
107
|
+
*
|
|
108
|
+
* A URL no route covers is none of our business — the IdP, another Pod — and is
|
|
109
|
+
* passed through as before. A URL whose origin is where the best route would
|
|
110
|
+
* have sent it is already its own physical address, so passing it through is
|
|
111
|
+
* exactly what that route would have done. Anything else means the client
|
|
112
|
+
* knows a shorter path and has none usable: sending the request to the
|
|
113
|
+
* canonical URL anyway would leave over a path this client cannot reach, and
|
|
114
|
+
* that has to be reported (with the candidates) instead of quietly attempted.
|
|
115
|
+
*/
|
|
116
|
+
const unreachablePassThrough = (input) => {
|
|
117
|
+
const url = requestUrl(input);
|
|
118
|
+
if (!url || !routeCovers(url)) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const routes = options.routes();
|
|
122
|
+
const preferred = usableRoutes({ canonicalUrl: routes[0]?.canonicalUrl ?? '', routes }, managedClient, options.allowLocalOnlyRoutes ?? false)[0];
|
|
123
|
+
if (!preferred || sameOrigin(preferred.targetUrl, url)) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return url;
|
|
127
|
+
};
|
|
69
128
|
return async (input, init) => {
|
|
70
129
|
const route = await routeFor();
|
|
71
130
|
if (!route) {
|
|
131
|
+
const unreachable = unreachablePassThrough(input);
|
|
132
|
+
if (unreachable) {
|
|
133
|
+
throw new NoUsableAccessRouteError(unreachable.href, options.routes());
|
|
134
|
+
}
|
|
72
135
|
return init === undefined
|
|
73
136
|
? options.fetch.call(globalThis, input)
|
|
74
137
|
: options.fetch.call(globalThis, input, init);
|
|
75
138
|
}
|
|
76
139
|
try {
|
|
77
|
-
|
|
140
|
+
const response = await routedFetchFor(route)(input, init);
|
|
141
|
+
failed = undefined;
|
|
142
|
+
return response;
|
|
78
143
|
}
|
|
79
144
|
catch (error) {
|
|
80
|
-
// The route went away (laptop moved networks, tunnel restarted
|
|
81
|
-
//
|
|
145
|
+
// The route went away (laptop moved networks, tunnel restarted, or the
|
|
146
|
+
// runtime reported a health it cannot back up). Ask for the current set
|
|
147
|
+
// once and retry against whatever is best now, without this one.
|
|
148
|
+
failed = routeKey(route);
|
|
82
149
|
selected = undefined;
|
|
83
150
|
await options.refreshRoutes?.();
|
|
84
151
|
const retryRoute = await selectRoute();
|
|
85
|
-
if (
|
|
86
|
-
|
|
152
|
+
if (retryRoute && routeKey(retryRoute) !== routeKey(route)) {
|
|
153
|
+
return routedFetchFor(retryRoute)(input, init);
|
|
87
154
|
}
|
|
88
|
-
|
|
155
|
+
// Nothing else can serve this request. When the URL was one this client
|
|
156
|
+
// only reaches through a route, the failure *is* "no usable route" — say
|
|
157
|
+
// so, with the transport failure kept as the reason, instead of letting
|
|
158
|
+
// the caller report whatever the canonical URL would have answered. A URL
|
|
159
|
+
// no route covers (the IdP, another Pod) keeps its own error.
|
|
160
|
+
const url = requestUrl(input);
|
|
161
|
+
if (url && routeCovers(url)) {
|
|
162
|
+
throw new NoUsableAccessRouteError(url.href, options.routes(), error);
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
89
165
|
}
|
|
90
166
|
};
|
|
91
167
|
}
|
|
168
|
+
/** Identity of one physical path: the same route id can point at a new target. */
|
|
169
|
+
function routeKey(route) {
|
|
170
|
+
return `${route.id}|${route.targetUrl}`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A URL this client can only reach through a route, with no route usable.
|
|
174
|
+
*
|
|
175
|
+
* Thrown instead of sending the request to its canonical address, because that
|
|
176
|
+
* address is not the path this client meant to take: something is wrong with the
|
|
177
|
+
* routes themselves (the node's own gateway is down, a tunnel is gone), and the
|
|
178
|
+
* caller has to say so rather than report whatever the canonical URL answers.
|
|
179
|
+
*/
|
|
180
|
+
export class NoUsableAccessRouteError extends Error {
|
|
181
|
+
constructor(url, routes,
|
|
182
|
+
/** The transport failure that left no route usable, when there was one. */
|
|
183
|
+
reason) {
|
|
184
|
+
super(`No usable access route can reach ${url}`);
|
|
185
|
+
this.url = url;
|
|
186
|
+
this.routes = routes;
|
|
187
|
+
this.reason = reason;
|
|
188
|
+
this.name = 'NoUsableAccessRouteError';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function toLocalRoute(route) {
|
|
192
|
+
return { canonicalBaseUrl: route.canonicalUrl, localBaseUrl: route.targetUrl };
|
|
193
|
+
}
|
|
194
|
+
function sameOrigin(value, url) {
|
|
195
|
+
try {
|
|
196
|
+
return new URL(value).origin === url.origin;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function requestUrl(input) {
|
|
203
|
+
if (input instanceof Request) {
|
|
204
|
+
try {
|
|
205
|
+
return new URL(input.url);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
return new URL(String(input));
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
92
218
|
async function probeWithTimeout(route, probe, timeoutMs) {
|
|
93
219
|
const controller = new AbortController();
|
|
94
220
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -102,6 +228,22 @@ async function probeWithTimeout(route, probe, timeoutMs) {
|
|
|
102
228
|
clearTimeout(timer);
|
|
103
229
|
}
|
|
104
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Whether a route's host answered at all.
|
|
233
|
+
*
|
|
234
|
+
* The probe asks the target host for one small document; what it measures is
|
|
235
|
+
* reachability, not whether that host implements this optional discovery path.
|
|
236
|
+
* A Pod server that answers `HEAD /.well-known/solid` with 404/405/501 (CSS does:
|
|
237
|
+
* 405 for `HEAD`, 501 for `GET`) is up and serving this route, and the request
|
|
238
|
+
* that follows carries the canonical URL plus its own authorization. Rejecting
|
|
239
|
+
* those answers leaves a same-machine client with no route whenever the public
|
|
240
|
+
* one is down — the one case local login has to survive
|
|
241
|
+
* (`docs/multi-channel-access.md`). A failing host (5xx, or a closed socket,
|
|
242
|
+
* which throws) still reports no route.
|
|
243
|
+
*/
|
|
244
|
+
function answersAccessRoute(response) {
|
|
245
|
+
return response.status < 500;
|
|
246
|
+
}
|
|
105
247
|
async function defaultProbe(route, signal) {
|
|
106
248
|
if (!route.targetUrl.startsWith('http://') && !route.targetUrl.startsWith('https://')) {
|
|
107
249
|
// Non-HTTP routes (p2p sockets) report their own readiness.
|
|
@@ -111,5 +253,5 @@ async function defaultProbe(route, signal) {
|
|
|
111
253
|
method: 'HEAD',
|
|
112
254
|
signal,
|
|
113
255
|
});
|
|
114
|
-
return response
|
|
256
|
+
return answersAccessRoute(response);
|
|
115
257
|
}
|
|
@@ -67,6 +67,7 @@ __export(exports_src, {
|
|
|
67
67
|
SOLID_LOCAL_ROUTE_CANONICAL_URL_HEADER: () => SOLID_LOCAL_ROUTE_CANONICAL_URL_HEADER,
|
|
68
68
|
SOLID_LOCAL_ROUTE_CANONICAL_ORIGIN_HEADER: () => SOLID_LOCAL_ROUTE_CANONICAL_ORIGIN_HEADER,
|
|
69
69
|
SOLID_LOCAL_ROUTE_CANONICAL_HOST_HEADER: () => SOLID_LOCAL_ROUTE_CANONICAL_HOST_HEADER,
|
|
70
|
+
NoUsableAccessRouteError: () => NoUsableAccessRouteError,
|
|
70
71
|
DEFAULT_PROVIDERS: () => DEFAULT_PROVIDERS
|
|
71
72
|
});
|
|
72
73
|
module.exports = __toCommonJS(exports_src);
|
|
@@ -1163,24 +1164,34 @@ async function chooseAccessRoute(routeSet, options = {}) {
|
|
|
1163
1164
|
if (candidates.length === 0) {
|
|
1164
1165
|
return null;
|
|
1165
1166
|
}
|
|
1167
|
+
const best = candidates[0];
|
|
1168
|
+
if (isSelfProvenRoute(best)) {
|
|
1169
|
+
return best;
|
|
1170
|
+
}
|
|
1166
1171
|
const probe = options.probe ?? defaultProbe;
|
|
1167
1172
|
const timeoutMs = options.probeTimeoutMs ?? 1000;
|
|
1168
1173
|
const results = await Promise.all(candidates.map(async (route) => ({
|
|
1169
1174
|
route,
|
|
1170
|
-
ok: await probeWithTimeout(route, probe, timeoutMs)
|
|
1175
|
+
ok: isSelfProvenRoute(route) || await probeWithTimeout(route, probe, timeoutMs)
|
|
1171
1176
|
})));
|
|
1172
1177
|
return results.find((result) => result.ok)?.route ?? null;
|
|
1173
1178
|
}
|
|
1179
|
+
function isSelfProvenRoute(route) {
|
|
1180
|
+
return route.health === "healthy";
|
|
1181
|
+
}
|
|
1174
1182
|
function usableRoutes(routeSet, managedClient, allowLocalOnlyRoutes) {
|
|
1175
1183
|
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));
|
|
1176
1184
|
}
|
|
1177
1185
|
function createSolidAccessRouteFetch(options) {
|
|
1178
1186
|
const managedClient = options.managedClient ?? true;
|
|
1179
1187
|
let selected;
|
|
1188
|
+
let failed;
|
|
1180
1189
|
const selectRoute = async () => {
|
|
1190
|
+
const all = options.routes();
|
|
1191
|
+
const routes = failed === undefined ? all : all.filter((route) => routeKey(route) !== failed);
|
|
1181
1192
|
const routeSet = {
|
|
1182
|
-
canonicalUrl:
|
|
1183
|
-
routes
|
|
1193
|
+
canonicalUrl: routes[0]?.canonicalUrl ?? "",
|
|
1194
|
+
routes
|
|
1184
1195
|
};
|
|
1185
1196
|
const candidate = await chooseAccessRoute(routeSet, {
|
|
1186
1197
|
managedClient,
|
|
@@ -1204,24 +1215,88 @@ function createSolidAccessRouteFetch(options) {
|
|
|
1204
1215
|
localBaseUrl: route.targetUrl
|
|
1205
1216
|
}]
|
|
1206
1217
|
});
|
|
1218
|
+
const routeCovers = (url) => Boolean(resolveSolidLocalRouteUrl(url, options.routes().map(toLocalRoute)));
|
|
1219
|
+
const unreachablePassThrough = (input) => {
|
|
1220
|
+
const url = requestUrl2(input);
|
|
1221
|
+
if (!url || !routeCovers(url)) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const routes = options.routes();
|
|
1225
|
+
const preferred = usableRoutes({ canonicalUrl: routes[0]?.canonicalUrl ?? "", routes }, managedClient, options.allowLocalOnlyRoutes ?? false)[0];
|
|
1226
|
+
if (!preferred || sameOrigin(preferred.targetUrl, url)) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
return url;
|
|
1230
|
+
};
|
|
1207
1231
|
return async (input, init) => {
|
|
1208
1232
|
const route = await routeFor();
|
|
1209
1233
|
if (!route) {
|
|
1234
|
+
const unreachable = unreachablePassThrough(input);
|
|
1235
|
+
if (unreachable) {
|
|
1236
|
+
throw new NoUsableAccessRouteError(unreachable.href, options.routes());
|
|
1237
|
+
}
|
|
1210
1238
|
return init === undefined ? options.fetch.call(globalThis, input) : options.fetch.call(globalThis, input, init);
|
|
1211
1239
|
}
|
|
1212
1240
|
try {
|
|
1213
|
-
|
|
1241
|
+
const response = await routedFetchFor(route)(input, init);
|
|
1242
|
+
failed = undefined;
|
|
1243
|
+
return response;
|
|
1214
1244
|
} catch (error) {
|
|
1245
|
+
failed = routeKey(route);
|
|
1215
1246
|
selected = undefined;
|
|
1216
1247
|
await options.refreshRoutes?.();
|
|
1217
1248
|
const retryRoute = await selectRoute();
|
|
1218
|
-
if (
|
|
1219
|
-
|
|
1249
|
+
if (retryRoute && routeKey(retryRoute) !== routeKey(route)) {
|
|
1250
|
+
return routedFetchFor(retryRoute)(input, init);
|
|
1251
|
+
}
|
|
1252
|
+
const url = requestUrl2(input);
|
|
1253
|
+
if (url && routeCovers(url)) {
|
|
1254
|
+
throw new NoUsableAccessRouteError(url.href, options.routes(), error);
|
|
1220
1255
|
}
|
|
1221
|
-
|
|
1256
|
+
throw error;
|
|
1222
1257
|
}
|
|
1223
1258
|
};
|
|
1224
1259
|
}
|
|
1260
|
+
function routeKey(route) {
|
|
1261
|
+
return `${route.id}|${route.targetUrl}`;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
class NoUsableAccessRouteError extends Error {
|
|
1265
|
+
url;
|
|
1266
|
+
routes;
|
|
1267
|
+
reason;
|
|
1268
|
+
constructor(url, routes, reason) {
|
|
1269
|
+
super(`No usable access route can reach ${url}`);
|
|
1270
|
+
this.url = url;
|
|
1271
|
+
this.routes = routes;
|
|
1272
|
+
this.reason = reason;
|
|
1273
|
+
this.name = "NoUsableAccessRouteError";
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function toLocalRoute(route) {
|
|
1277
|
+
return { canonicalBaseUrl: route.canonicalUrl, localBaseUrl: route.targetUrl };
|
|
1278
|
+
}
|
|
1279
|
+
function sameOrigin(value, url) {
|
|
1280
|
+
try {
|
|
1281
|
+
return new URL(value).origin === url.origin;
|
|
1282
|
+
} catch {
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
function requestUrl2(input) {
|
|
1287
|
+
if (input instanceof Request) {
|
|
1288
|
+
try {
|
|
1289
|
+
return new URL(input.url);
|
|
1290
|
+
} catch {
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
try {
|
|
1295
|
+
return new URL(String(input));
|
|
1296
|
+
} catch {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1225
1300
|
async function probeWithTimeout(route, probe, timeoutMs) {
|
|
1226
1301
|
const controller = new AbortController;
|
|
1227
1302
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -1233,6 +1308,9 @@ async function probeWithTimeout(route, probe, timeoutMs) {
|
|
|
1233
1308
|
clearTimeout(timer);
|
|
1234
1309
|
}
|
|
1235
1310
|
}
|
|
1311
|
+
function answersAccessRoute(response) {
|
|
1312
|
+
return response.status < 500;
|
|
1313
|
+
}
|
|
1236
1314
|
async function defaultProbe(route, signal) {
|
|
1237
1315
|
if (!route.targetUrl.startsWith("http://") && !route.targetUrl.startsWith("https://")) {
|
|
1238
1316
|
return route.health === "healthy";
|
|
@@ -1241,5 +1319,5 @@ async function defaultProbe(route, signal) {
|
|
|
1241
1319
|
method: "HEAD",
|
|
1242
1320
|
signal
|
|
1243
1321
|
});
|
|
1244
|
-
return response
|
|
1322
|
+
return answersAccessRoute(response);
|
|
1245
1323
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undefineds.co/xpod",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.18",
|
|
4
4
|
"description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
|
|
5
5
|
"repository": "https://github.com/undefinedsco/xpod",
|
|
6
6
|
"author": "developer@undefineds.co",
|
|
@@ -385,7 +385,7 @@
|
|
|
385
385
|
}
|
|
386
386
|
},
|
|
387
387
|
"optionalDependencies": {
|
|
388
|
-
"@undefineds.co/xpod-darwin-arm64": "0.4.
|
|
388
|
+
"@undefineds.co/xpod-darwin-arm64": "0.4.18"
|
|
389
389
|
},
|
|
390
390
|
"resolutions": {
|
|
391
391
|
"@inrupt/solid-client-authn-browser": "^3.1.1",
|