@undefineds.co/xpod 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +178 -194
  2. package/node_modules/@undefineds.co/solid-sdk/dist/pod-runtime.js +6 -13
  3. package/node_modules/@undefineds.co/solid-sdk/dist/storage-selection.js +6 -21
  4. package/node_modules/@undefineds.co/solid-sdk/dist/webid-auth.d.ts +2 -0
  5. package/node_modules/@undefineds.co/solid-sdk/dist/webid-auth.js +8 -3
  6. package/package.json +14 -2
  7. package/static/app/assets/main.js +10 -10
  8. package/static/auth-callback/assets/{AiConfigContext-CRxnTyNS.js → AiConfigContext-DJKvxduw.js} +1 -1
  9. package/static/auth-callback/assets/{AiConfigPage-aidVtDAU.js → AiConfigPage-DgGmHb18.js} +1 -1
  10. package/static/auth-callback/assets/{DocumentProcessingPanel-C-lnRpEy.js → DocumentProcessingPanel-CJmxu8x1.js} +1 -1
  11. package/static/auth-callback/assets/{IndexLifecyclePanel-8oscxWj2.js → IndexLifecyclePanel-byCa7h7L.js} +1 -1
  12. package/static/auth-callback/assets/{IndexSubjectPanel-ybKkidfS.js → IndexSubjectPanel-CDwT4Yx1.js} +1 -1
  13. package/static/auth-callback/assets/{ModelAssignmentsPanel-DmxLA1Da.js → ModelAssignmentsPanel-CwM3k8w7.js} +1 -1
  14. package/static/auth-callback/assets/{ModelsPage-B-G4YfTv.js → ModelsPage-DapY2Dwf.js} +1 -1
  15. package/static/auth-callback/assets/{NetworkPage-W890Vczu.js → NetworkPage-DkVgxtNj.js} +1 -1
  16. package/static/auth-callback/assets/{PaneListHeader-Aj8ayvMU.js → PaneListHeader-DRI0hjmt.js} +1 -1
  17. package/static/auth-callback/assets/{SearchIndexingPanel-DTyOKrU1.js → SearchIndexingPanel-mbppDZFE.js} +1 -1
  18. package/static/auth-callback/assets/{StatusSubjectPanel-DT1Aw7YO.js → StatusSubjectPanel-DNsW76aC.js} +1 -1
  19. package/static/auth-callback/assets/{StatusWorkspace-BO7YcO6a.js → StatusWorkspace-DGcTbHT5.js} +1 -1
  20. package/static/auth-callback/assets/{SystemSettingsPage-DfU_qHHq.js → SystemSettingsPage-C6qklJ9k.js} +1 -1
  21. package/static/auth-callback/assets/{SystemSettingsSubjectPanel-nfcG67Tx.js → SystemSettingsSubjectPanel-B4VV9xkG.js} +1 -1
  22. package/static/auth-callback/assets/{activity-CPUh0Hwy.js → activity-Ck-Mfxxo.js} +1 -1
  23. package/static/auth-callback/assets/{auth-callback-rq-93B6I.js → auth-callback-DG4Ge40c.js} +6 -6
  24. package/static/auth-callback/assets/{badge-C8cHw8zP.js → badge-GSSnw1VV.js} +1 -1
  25. package/static/auth-callback/assets/{download-C7i9MWS2.js → download-BYyRX-K2.js} +1 -1
  26. package/static/auth-callback/assets/{external-link-BrwSigZD.js → external-link-WEC0Ki-0.js} +1 -1
  27. package/static/auth-callback/assets/{index-DSY0DCJV.js → index-DlsMCgo8.js} +1 -1
  28. package/static/auth-callback/assets/{index-browser-CMOmp0rf.js → index-browser-D8YW6coF.js} +1 -1
  29. package/static/auth-callback/assets/{index-Drnlv8Gw.js → index-kju2k4Dp.js} +1 -1
  30. package/static/auth-callback/assets/{key-round-DO8fiDNf.js → key-round-DJUT5Nh0.js} +1 -1
  31. package/static/auth-callback/assets/{rotate-ccw-DAOErQ0P.js → rotate-ccw-CXwfZUKU.js} +1 -1
  32. package/static/auth-callback/assets/{skeleton-poTd6JSC.js → skeleton-DWPtNhUB.js} +1 -1
  33. package/static/auth-callback/assets/{waypoints-Bg9TQvJS.js → waypoints-l1oDnRJ0.js} +1 -1
  34. package/static/auth-callback/assets/{workspace-layout-Dmb-DfHy.js → workspace-layout-B7iKpK-Q.js} +1 -1
  35. package/static/auth-callback/auth-callback.html +1 -1
  36. package/static/dashboard/assets/{AiConfigContext-Ds7MIAm6.js → AiConfigContext-U0cCSY28.js} +1 -1
  37. package/static/dashboard/assets/{AiConfigPage-CRrI_NnS.js → AiConfigPage-CMi0uJdo.js} +1 -1
  38. package/static/dashboard/assets/{DocumentProcessingPanel-BVFIT_Zl.js → DocumentProcessingPanel-DEZiEpft.js} +1 -1
  39. package/static/dashboard/assets/{IndexLifecyclePanel-BSzivp60.js → IndexLifecyclePanel-CGZfLUog.js} +1 -1
  40. package/static/dashboard/assets/{IndexSubjectPanel-B5zQeqWl.js → IndexSubjectPanel-DiDZauHk.js} +1 -1
  41. package/static/dashboard/assets/{ModelAssignmentsPanel-B-1FWxn0.js → ModelAssignmentsPanel-DaGGZKuf.js} +1 -1
  42. package/static/dashboard/assets/{ModelsPage-B3VYhX9h.js → ModelsPage-DZgAOdxO.js} +1 -1
  43. package/static/dashboard/assets/{NetworkPage-HJqcYjfO.js → NetworkPage-DI5-O7GT.js} +1 -1
  44. package/static/dashboard/assets/{PaneListHeader-DiFB-pBl.js → PaneListHeader-4oa7uf59.js} +1 -1
  45. package/static/dashboard/assets/{SearchIndexingPanel-BnA0kOD9.js → SearchIndexingPanel-DoqEwocD.js} +1 -1
  46. package/static/dashboard/assets/{StatusSubjectPanel-tAB7Scqn.js → StatusSubjectPanel-CLzBC5fu.js} +1 -1
  47. package/static/dashboard/assets/{StatusWorkspace-kNMe8nOB.js → StatusWorkspace-UyKADZoM.js} +1 -1
  48. package/static/dashboard/assets/{SystemSettingsPage-8U1VrI_f.js → SystemSettingsPage-qP0hLHNV.js} +1 -1
  49. package/static/dashboard/assets/{SystemSettingsSubjectPanel-goh78mjo.js → SystemSettingsSubjectPanel-EaWax9Ot.js} +1 -1
  50. package/static/dashboard/assets/{activity-DNXbcx1t.js → activity-CjIoNU4S.js} +1 -1
  51. package/static/dashboard/assets/{badge-DR3YwIcl.js → badge-dcDZpKh3.js} +1 -1
  52. package/static/dashboard/assets/{dashboard-C64rEkaL.js → dashboard-BY8GdnEI.js} +5 -5
  53. package/static/dashboard/assets/{download-BwyvySqB.js → download-BeYCrc-_.js} +1 -1
  54. package/static/dashboard/assets/{external-link-Dmez6yTy.js → external-link-626-5KVx.js} +1 -1
  55. package/static/dashboard/assets/{index-N1onOEV6.js → index-CBbk6hx-.js} +1 -1
  56. package/static/dashboard/assets/{index-BAkEY7c3.js → index-CsxH6Hmz.js} +1 -1
  57. package/static/dashboard/assets/{key-round-BM6zS_FJ.js → key-round-B9IL4Wua.js} +1 -1
  58. package/static/dashboard/assets/{rotate-ccw-D6BDXyhE.js → rotate-ccw-DM4vCfiI.js} +1 -1
  59. package/static/dashboard/assets/{skeleton-Dy9nFkSL.js → skeleton-ChlFAPvP.js} +1 -1
  60. package/static/dashboard/assets/{waypoints-GzW4F2Ei.js → waypoints-CvpZzf_I.js} +1 -1
  61. package/static/dashboard/assets/{workspace-layout-D6jwN-1q.js → workspace-layout-Bxv7V-tt.js} +1 -1
  62. package/static/dashboard/dashboard.html +1 -1
  63. package/static/settings/assets/{AiConfigContext-CwPOEIt2.js → AiConfigContext-CDVIHLBb.js} +1 -1
  64. package/static/settings/assets/{AiConfigPage-BSUTkt12.js → AiConfigPage-C7qfiiaJ.js} +1 -1
  65. package/static/settings/assets/{DocumentProcessingPanel-Bsrmh_jc.js → DocumentProcessingPanel-B59QTe-V.js} +1 -1
  66. package/static/settings/assets/{IndexLifecyclePanel-CqG9oal4.js → IndexLifecyclePanel-C-e-3eVi.js} +1 -1
  67. package/static/settings/assets/{IndexSubjectPanel-C81n07Gj.js → IndexSubjectPanel-B-scRZ1u.js} +1 -1
  68. package/static/settings/assets/{ModelAssignmentsPanel-CwUbqhcc.js → ModelAssignmentsPanel-Wy8jzVg4.js} +1 -1
  69. package/static/settings/assets/{ModelsPage-BMs6BLrm.js → ModelsPage-DHNiaodk.js} +1 -1
  70. package/static/settings/assets/{NetworkPage-DtjeHtL4.js → NetworkPage-tBB-4Ox8.js} +1 -1
  71. package/static/settings/assets/{PaneListHeader-CO0oaVu8.js → PaneListHeader-DuD6glzn.js} +1 -1
  72. package/static/settings/assets/{SearchIndexingPanel-BgQfRVsf.js → SearchIndexingPanel-CiDHc0nx.js} +1 -1
  73. package/static/settings/assets/{StatusSubjectPanel-DJn3ZOvK.js → StatusSubjectPanel-OF5cgWwt.js} +1 -1
  74. package/static/settings/assets/{StatusWorkspace-C_CSF842.js → StatusWorkspace-CZ8Oa0gV.js} +1 -1
  75. package/static/settings/assets/{SystemSettingsPage-Oigv0uvH.js → SystemSettingsPage-DT2tC0F_.js} +1 -1
  76. package/static/settings/assets/{SystemSettingsSubjectPanel-Bc8qCKlD.js → SystemSettingsSubjectPanel-Bkta1oDw.js} +1 -1
  77. package/static/settings/assets/{activity-D0ZeHnaN.js → activity-2M1f5eBS.js} +1 -1
  78. package/static/settings/assets/{badge-qUNztkFM.js → badge-CRKan0qX.js} +1 -1
  79. package/static/settings/assets/{download-Cqf-BduE.js → download-PCHldCdu.js} +1 -1
  80. package/static/settings/assets/{external-link-IuWJ6OjY.js → external-link-tmKe7eYO.js} +1 -1
  81. package/static/settings/assets/{index-D3o0Tuj2.js → index-BDCNTwWz.js} +1 -1
  82. package/static/settings/assets/{index-DRjSKNcs.js → index-Cek02M79.js} +1 -1
  83. package/static/settings/assets/{index-browser-D9qFZqYE.js → index-browser-EVDVm9T5.js} +1 -1
  84. package/static/settings/assets/{key-round-KxL3N2jU.js → key-round-CUGvQCkt.js} +1 -1
  85. package/static/settings/assets/{rotate-ccw-BiW1Onno.js → rotate-ccw-BRA3lIHM.js} +1 -1
  86. package/static/settings/assets/{settings-DH2ML86C.js → settings-BdlKrzJ3.js} +6 -6
  87. package/static/settings/assets/{skeleton-CsUPnZPX.js → skeleton-CuDeifWb.js} +1 -1
  88. package/static/settings/assets/{waypoints-xuODoy-1.js → waypoints-w6LxsFY1.js} +1 -1
  89. package/static/settings/assets/{workspace-layout-O7swKpQ6.js → workspace-layout-TN-BWmT5.js} +1 -1
  90. package/static/settings/settings.html +1 -1
@@ -39,6 +39,7 @@ var __export = (target, all) => {
39
39
  // src/index.ts
40
40
  var exports_src = {};
41
41
  __export(exports_src, {
42
+ validateWebId: () => validateWebId,
42
43
  useSolidSessionSnapshot: () => useSolidSessionSnapshot,
43
44
  useSolidRuntime: () => useSolidRuntime,
44
45
  useLoginStore: () => useLoginStore,
@@ -228,16 +229,178 @@ function createSolidSessionRuntime(options = {}) {
228
229
  }
229
230
  };
230
231
  }
231
- // src/pod-runtime.ts
232
- var KEY_SEPARATOR = "\x00";
233
- function normalizeIdentityPart(value) {
234
- const trimmed = value.trim();
232
+ // src/webid-auth.ts
233
+ var ENDPOINT_PROTOCOLS = new Set(["http:", "https:"]);
234
+ var RESERVED_AUTHORIZATION_PARAMETERS = new Set([
235
+ "state",
236
+ "redirect_uri",
237
+ "client_id",
238
+ "response_type",
239
+ "code_challenge",
240
+ "code_challenge_method"
241
+ ]);
242
+ function normalizeEndpoint(endpoint, fieldName) {
243
+ if (!endpoint || typeof endpoint.url !== "string" || endpoint.url.trim() === "") {
244
+ throw new TypeError(`${fieldName}.url must be a non-empty absolute URL`);
245
+ }
246
+ if (typeof endpoint.label !== "string") {
247
+ throw new TypeError(`${fieldName}.label must be a string`);
248
+ }
249
+ let parsed;
250
+ try {
251
+ parsed = new URL(endpoint.url.trim());
252
+ } catch {
253
+ throw new TypeError(`${fieldName}.url must be a valid absolute URL`);
254
+ }
255
+ if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
256
+ throw new TypeError(`${fieldName}.url must use http or https`);
257
+ }
258
+ if (parsed.username || parsed.password) {
259
+ throw new TypeError(`${fieldName}.url must not contain credentials`);
260
+ }
261
+ if (parsed.hash) {
262
+ throw new TypeError(`${fieldName}.url must not contain a fragment`);
263
+ }
264
+ return {
265
+ url: parsed.href,
266
+ label: endpoint.label
267
+ };
268
+ }
269
+ function validateWebId(value) {
270
+ if (typeof value !== "string" || value === "" || /[\s\u0000-\u001f\u007f-\u009f]/u.test(value)) {
271
+ throw new TypeError("webId must be a non-empty identifier without whitespace or control characters");
272
+ }
273
+ return value;
274
+ }
275
+ function normalizeStorageBinding(binding) {
276
+ if (!binding || typeof binding.storageUrl !== "string" || binding.storageUrl.trim() === "") {
277
+ throw new TypeError("selectedStorage.storageUrl must be a non-empty absolute URL");
278
+ }
279
+ validateWebId(binding.webId);
280
+ let parsed;
235
281
  try {
236
- return new URL(trimmed).href;
282
+ parsed = new URL(binding.storageUrl.trim());
237
283
  } catch {
238
- return trimmed;
284
+ throw new TypeError("selectedStorage.storageUrl must be a valid absolute URL");
285
+ }
286
+ if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
287
+ throw new TypeError("selectedStorage.storageUrl must use http or https");
288
+ }
289
+ if (parsed.username || parsed.password) {
290
+ throw new TypeError("selectedStorage.storageUrl must not contain credentials");
291
+ }
292
+ if (parsed.hash) {
293
+ throw new TypeError("selectedStorage.storageUrl must not contain a fragment");
294
+ }
295
+ return {
296
+ storageUrl: parsed.href,
297
+ webId: binding.webId,
298
+ ...binding.label === undefined ? {} : { label: binding.label }
299
+ };
300
+ }
301
+ function ensureEnum(value, values, fieldName) {
302
+ if (!values.includes(value)) {
303
+ throw new TypeError(`${fieldName} has an unsupported value`);
304
+ }
305
+ return value;
306
+ }
307
+ function normalizeWebIdLoginRoute(input) {
308
+ if (!input || typeof input.id !== "string" || input.id.trim() === "") {
309
+ throw new TypeError("route.id must be a non-empty opaque identifier");
310
+ }
311
+ if (typeof input.label !== "string") {
312
+ throw new TypeError("route.label must be a string");
313
+ }
314
+ const normalized = {
315
+ id: input.id.trim(),
316
+ label: input.label,
317
+ ...input.description === undefined ? {} : { description: input.description },
318
+ ...input.badge === undefined ? {} : {
319
+ badge: {
320
+ label: input.badge.label,
321
+ tone: ensureEnum(input.badge.tone, ["neutral", "primary", "success", "warning", "danger"], "route.badge.tone")
322
+ }
323
+ },
324
+ identityProvider: normalizeEndpoint(input.identityProvider, "route.identityProvider"),
325
+ ...input.storageProvider === undefined ? {} : {
326
+ storageProvider: normalizeEndpoint(input.storageProvider, "route.storageProvider")
327
+ },
328
+ availability: ensureEnum(input.availability, ["ready", "starting", "unavailable"], "route.availability"),
329
+ ...input.unavailableReason === undefined ? {} : { unavailableReason: input.unavailableReason }
330
+ };
331
+ return normalized;
332
+ }
333
+ function decodeForValidation(value) {
334
+ let decoded = value;
335
+ for (let index = 0;index < 4; index += 1) {
336
+ let next;
337
+ try {
338
+ next = decodeURIComponent(decoded);
339
+ } catch {
340
+ throw new TypeError("returnTo contains malformed percent encoding");
341
+ }
342
+ if (next === decoded) {
343
+ return decoded;
344
+ }
345
+ decoded = next;
346
+ }
347
+ return decoded;
348
+ }
349
+ function matchesAllowedPrefix(pathname, allowedPrefixes) {
350
+ return allowedPrefixes.some((prefix) => {
351
+ if (prefix === "/") {
352
+ return pathname.startsWith("/");
353
+ }
354
+ const normalizedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
355
+ return pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`);
356
+ });
357
+ }
358
+ function normalizeApplicationReturnTo(value, allowedPrefixes) {
359
+ if (value === undefined) {
360
+ return;
361
+ }
362
+ if (typeof value !== "string" || value.trim() === "") {
363
+ throw new TypeError("returnTo must be a non-empty application path");
364
+ }
365
+ const normalized = value.trim();
366
+ const decoded = decodeForValidation(normalized);
367
+ const pathname = decoded.split(/[?#]/, 1)[0];
368
+ if (!normalized.startsWith("/") || normalized.startsWith("//") || normalized.includes("\\") || decoded.includes("\\") || /^[a-z][a-z\d+.-]*:/i.test(decoded) || decoded.startsWith("//") || pathname.split("/").some((segment) => segment === "..") || !matchesAllowedPrefix(pathname, allowedPrefixes)) {
369
+ throw new TypeError("returnTo must be a safe path within the application allow-list");
370
+ }
371
+ return normalized;
372
+ }
373
+ function normalizeWebIdLoginTransaction(input) {
374
+ if (!input || typeof input.id !== "string" || input.id.trim() === "") {
375
+ throw new TypeError("transaction.id must be a non-empty opaque identifier");
376
+ }
377
+ const authorizationParameters = input.authorizationParameters === undefined ? undefined : { ...input.authorizationParameters };
378
+ if (authorizationParameters) {
379
+ for (const key of Object.keys(authorizationParameters)) {
380
+ if (RESERVED_AUTHORIZATION_PARAMETERS.has(key.toLowerCase())) {
381
+ throw new TypeError(`authorizationParameters cannot override ${key}`);
382
+ }
383
+ }
239
384
  }
385
+ const prompt = input.prompt === undefined ? undefined : ensureEnum(input.prompt, ["login", "consent", "select_account"], "transaction.prompt");
386
+ return {
387
+ id: input.id,
388
+ route: normalizeWebIdLoginRoute(input.route),
389
+ ...input.selectedStorage === undefined ? {} : {
390
+ selectedStorage: normalizeStorageBinding(input.selectedStorage)
391
+ },
392
+ authorizationSurface: ensureEnum(input.authorizationSurface, ["redirect", "popup", "embedded", "external"], "transaction.authorizationSurface"),
393
+ ...prompt === undefined ? {} : { prompt },
394
+ discovery: ensureEnum(input.discovery, ["standard", "strict"], "transaction.discovery"),
395
+ ...authorizationParameters === undefined ? {} : { authorizationParameters },
396
+ ...input.returnTo === undefined ? {} : {
397
+ returnTo: normalizeApplicationReturnTo(input.returnTo, ["/"])
398
+ }
399
+ };
240
400
  }
401
+
402
+ // src/pod-runtime.ts
403
+ var KEY_SEPARATOR = "\x00";
241
404
  function normalizePodUrl(value) {
242
405
  if (typeof value !== "string" || value.trim() === "") {
243
406
  throw new TypeError("podUrl must be a non-empty absolute URL");
@@ -257,7 +420,7 @@ function normalizePodUrl(value) {
257
420
  return parsed.href;
258
421
  }
259
422
  function compositeKey(webId, podUrl) {
260
- return `${normalizeIdentityPart(webId)}${KEY_SEPARATOR}${normalizeIdentityPart(podUrl)}`;
423
+ return `${webId}${KEY_SEPARATOR}${podUrl}`;
261
424
  }
262
425
  function nextGeneration(generations, key) {
263
426
  const generation = (generations.get(key) ?? 0) + 1;
@@ -278,7 +441,7 @@ function createPodRuntime(options) {
278
441
  const webIdGenerations = new Map;
279
442
  const compositeGenerations = new Map;
280
443
  const clearWebId = (webId, podUrl) => {
281
- const webIdKey = normalizeIdentityPart(webId);
444
+ const webIdKey = validateWebId(webId);
282
445
  if (podUrl !== undefined) {
283
446
  const normalizedPodUrl = normalizePodUrl(podUrl);
284
447
  const key = compositeKey(webId, normalizedPodUrl);
@@ -315,7 +478,7 @@ function createPodRuntime(options) {
315
478
  }
316
479
  };
317
480
  const clear = (identity) => {
318
- if (!identity) {
481
+ if (identity === undefined) {
319
482
  readyByWebId.clear();
320
483
  readyByComposite.clear();
321
484
  for (const pending of pendingByWebId.values()) {
@@ -342,7 +505,7 @@ function createPodRuntime(options) {
342
505
  };
343
506
  return {
344
507
  open(args) {
345
- const webIdKey = normalizeIdentityPart(args.webId);
508
+ const webIdKey = validateWebId(args.webId);
346
509
  const explicitPodUrl = args.podUrl === undefined ? undefined : normalizePodUrl(args.podUrl);
347
510
  if (explicitPodUrl !== undefined) {
348
511
  const key = compositeKey(args.webId, explicitPodUrl);
@@ -492,189 +655,10 @@ function useSolidRuntime() {
492
655
  function useSolidSessionSnapshot() {
493
656
  return useSolidRuntime().session.getSnapshot();
494
657
  }
495
- // src/webid-auth.ts
496
- var ENDPOINT_PROTOCOLS = new Set(["http:", "https:"]);
497
- var RESERVED_AUTHORIZATION_PARAMETERS = new Set([
498
- "state",
499
- "redirect_uri",
500
- "client_id",
501
- "response_type",
502
- "code_challenge",
503
- "code_challenge_method"
504
- ]);
505
- function normalizeEndpoint(endpoint, fieldName) {
506
- if (!endpoint || typeof endpoint.url !== "string" || endpoint.url.trim() === "") {
507
- throw new TypeError(`${fieldName}.url must be a non-empty absolute URL`);
508
- }
509
- if (typeof endpoint.label !== "string") {
510
- throw new TypeError(`${fieldName}.label must be a string`);
511
- }
512
- let parsed;
513
- try {
514
- parsed = new URL(endpoint.url.trim());
515
- } catch {
516
- throw new TypeError(`${fieldName}.url must be a valid absolute URL`);
517
- }
518
- if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
519
- throw new TypeError(`${fieldName}.url must use http or https`);
520
- }
521
- if (parsed.username || parsed.password) {
522
- throw new TypeError(`${fieldName}.url must not contain credentials`);
523
- }
524
- if (parsed.hash) {
525
- throw new TypeError(`${fieldName}.url must not contain a fragment`);
526
- }
527
- return {
528
- url: parsed.href,
529
- label: endpoint.label
530
- };
531
- }
532
- function normalizeStorageBinding(binding) {
533
- if (!binding || typeof binding.storageUrl !== "string" || binding.storageUrl.trim() === "") {
534
- throw new TypeError("selectedStorage.storageUrl must be a non-empty absolute URL");
535
- }
536
- if (typeof binding.webId !== "string" || binding.webId.trim() === "") {
537
- throw new TypeError("selectedStorage.webId must be a non-empty identifier URL");
538
- }
539
- let parsed;
540
- try {
541
- parsed = new URL(binding.storageUrl.trim());
542
- } catch {
543
- throw new TypeError("selectedStorage.storageUrl must be a valid absolute URL");
544
- }
545
- if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
546
- throw new TypeError("selectedStorage.storageUrl must use http or https");
547
- }
548
- if (parsed.username || parsed.password) {
549
- throw new TypeError("selectedStorage.storageUrl must not contain credentials");
550
- }
551
- if (parsed.hash) {
552
- throw new TypeError("selectedStorage.storageUrl must not contain a fragment");
553
- }
554
- return {
555
- storageUrl: parsed.href,
556
- webId: binding.webId,
557
- ...binding.label === undefined ? {} : { label: binding.label }
558
- };
559
- }
560
- function ensureEnum(value, values, fieldName) {
561
- if (!values.includes(value)) {
562
- throw new TypeError(`${fieldName} has an unsupported value`);
563
- }
564
- return value;
565
- }
566
- function normalizeWebIdLoginRoute(input) {
567
- if (!input || typeof input.id !== "string" || input.id.trim() === "") {
568
- throw new TypeError("route.id must be a non-empty opaque identifier");
569
- }
570
- if (typeof input.label !== "string") {
571
- throw new TypeError("route.label must be a string");
572
- }
573
- const normalized = {
574
- id: input.id.trim(),
575
- label: input.label,
576
- ...input.description === undefined ? {} : { description: input.description },
577
- ...input.badge === undefined ? {} : {
578
- badge: {
579
- label: input.badge.label,
580
- tone: ensureEnum(input.badge.tone, ["neutral", "primary", "success", "warning", "danger"], "route.badge.tone")
581
- }
582
- },
583
- identityProvider: normalizeEndpoint(input.identityProvider, "route.identityProvider"),
584
- ...input.storageProvider === undefined ? {} : {
585
- storageProvider: normalizeEndpoint(input.storageProvider, "route.storageProvider")
586
- },
587
- availability: ensureEnum(input.availability, ["ready", "starting", "unavailable"], "route.availability"),
588
- ...input.unavailableReason === undefined ? {} : { unavailableReason: input.unavailableReason }
589
- };
590
- return normalized;
591
- }
592
- function decodeForValidation(value) {
593
- let decoded = value;
594
- for (let index = 0;index < 4; index += 1) {
595
- let next;
596
- try {
597
- next = decodeURIComponent(decoded);
598
- } catch {
599
- throw new TypeError("returnTo contains malformed percent encoding");
600
- }
601
- if (next === decoded) {
602
- return decoded;
603
- }
604
- decoded = next;
605
- }
606
- return decoded;
607
- }
608
- function matchesAllowedPrefix(pathname, allowedPrefixes) {
609
- return allowedPrefixes.some((prefix) => {
610
- if (prefix === "/") {
611
- return pathname.startsWith("/");
612
- }
613
- const normalizedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
614
- return pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`);
615
- });
616
- }
617
- function normalizeApplicationReturnTo(value, allowedPrefixes) {
618
- if (value === undefined) {
619
- return;
620
- }
621
- if (typeof value !== "string" || value.trim() === "") {
622
- throw new TypeError("returnTo must be a non-empty application path");
623
- }
624
- const normalized = value.trim();
625
- const decoded = decodeForValidation(normalized);
626
- const pathname = decoded.split(/[?#]/, 1)[0];
627
- if (!normalized.startsWith("/") || normalized.startsWith("//") || normalized.includes("\\") || decoded.includes("\\") || /^[a-z][a-z\d+.-]*:/i.test(decoded) || decoded.startsWith("//") || pathname.split("/").some((segment) => segment === "..") || !matchesAllowedPrefix(pathname, allowedPrefixes)) {
628
- throw new TypeError("returnTo must be a safe path within the application allow-list");
629
- }
630
- return normalized;
631
- }
632
- function normalizeWebIdLoginTransaction(input) {
633
- if (!input || typeof input.id !== "string" || input.id.trim() === "") {
634
- throw new TypeError("transaction.id must be a non-empty opaque identifier");
635
- }
636
- const authorizationParameters = input.authorizationParameters === undefined ? undefined : { ...input.authorizationParameters };
637
- if (authorizationParameters) {
638
- for (const key of Object.keys(authorizationParameters)) {
639
- if (RESERVED_AUTHORIZATION_PARAMETERS.has(key.toLowerCase())) {
640
- throw new TypeError(`authorizationParameters cannot override ${key}`);
641
- }
642
- }
643
- }
644
- const prompt = input.prompt === undefined ? undefined : ensureEnum(input.prompt, ["login", "consent", "select_account"], "transaction.prompt");
645
- return {
646
- id: input.id,
647
- route: normalizeWebIdLoginRoute(input.route),
648
- ...input.selectedStorage === undefined ? {} : {
649
- selectedStorage: normalizeStorageBinding(input.selectedStorage)
650
- },
651
- authorizationSurface: ensureEnum(input.authorizationSurface, ["redirect", "popup", "embedded", "external"], "transaction.authorizationSurface"),
652
- ...prompt === undefined ? {} : { prompt },
653
- discovery: ensureEnum(input.discovery, ["standard", "strict"], "transaction.discovery"),
654
- ...authorizationParameters === undefined ? {} : { authorizationParameters },
655
- ...input.returnTo === undefined ? {} : {
656
- returnTo: normalizeApplicationReturnTo(input.returnTo, ["/"])
657
- }
658
- };
659
- }
660
658
  // src/storage-selection.ts
661
- function normalizeIdentity(value) {
662
- const trimmed = value.trim();
663
- try {
664
- return new URL(trimmed).href;
665
- } catch {
666
- return trimmed;
667
- }
668
- }
669
- function normalizedBinding(binding) {
670
- return {
671
- ...normalizeStorageBinding(binding),
672
- webId: normalizeIdentity(binding.webId)
673
- };
674
- }
675
659
  function storageBindingMatches(expected, actual) {
676
- const left = normalizedBinding(expected);
677
- const right = normalizedBinding(actual);
660
+ const left = normalizeStorageBinding(expected);
661
+ const right = normalizeStorageBinding(actual);
678
662
  return left.storageUrl === right.storageUrl && left.webId === right.webId;
679
663
  }
680
664
  function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], positionalRemembered) {
@@ -692,8 +676,8 @@ function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], po
692
676
  if (options.webId === undefined || options.candidates === undefined) {
693
677
  return;
694
678
  }
695
- const webId = normalizeIdentity(options.webId);
696
- const candidates = options.candidates.map(normalizedBinding);
679
+ const webId = validateWebId(options.webId);
680
+ const candidates = options.candidates.map(normalizeStorageBinding);
697
681
  const seenByStorage = new Map;
698
682
  const uniqueCandidates = [];
699
683
  for (const candidate of candidates) {
@@ -711,7 +695,7 @@ function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], po
711
695
  }
712
696
  const eligible = uniqueCandidates.filter((candidate) => candidate.webId === webId);
713
697
  if (options.remembered) {
714
- const remembered = normalizedBinding(options.remembered);
698
+ const remembered = normalizeStorageBinding(options.remembered);
715
699
  const exact = eligible.find((candidate) => storageBindingMatches(candidate, remembered));
716
700
  if (exact) {
717
701
  return { status: "ready", selected: { ...exact } };
@@ -1,13 +1,5 @@
1
+ import { validateWebId } from './webid-auth.js';
1
2
  const KEY_SEPARATOR = '\u0000';
2
- function normalizeIdentityPart(value) {
3
- const trimmed = value.trim();
4
- try {
5
- return new URL(trimmed).href;
6
- }
7
- catch {
8
- return trimmed;
9
- }
10
- }
11
3
  function normalizePodUrl(value) {
12
4
  if (typeof value !== 'string' || value.trim() === '') {
13
5
  throw new TypeError('podUrl must be a non-empty absolute URL');
@@ -28,7 +20,8 @@ function normalizePodUrl(value) {
28
20
  return parsed.href;
29
21
  }
30
22
  function compositeKey(webId, podUrl) {
31
- return `${normalizeIdentityPart(webId)}${KEY_SEPARATOR}${normalizeIdentityPart(podUrl)}`;
23
+ // Only Pod addresses are normalized. The complete WebID is the identity.
24
+ return `${webId}${KEY_SEPARATOR}${podUrl}`;
32
25
  }
33
26
  function nextGeneration(generations, key) {
34
27
  const generation = (generations.get(key) ?? 0) + 1;
@@ -51,7 +44,7 @@ export function createPodRuntime(options) {
51
44
  const webIdGenerations = new Map();
52
45
  const compositeGenerations = new Map();
53
46
  const clearWebId = (webId, podUrl) => {
54
- const webIdKey = normalizeIdentityPart(webId);
47
+ const webIdKey = validateWebId(webId);
55
48
  if (podUrl !== undefined) {
56
49
  const normalizedPodUrl = normalizePodUrl(podUrl);
57
50
  const key = compositeKey(webId, normalizedPodUrl);
@@ -91,7 +84,7 @@ export function createPodRuntime(options) {
91
84
  }
92
85
  };
93
86
  const clear = (identity) => {
94
- if (!identity) {
87
+ if (identity === undefined) {
95
88
  readyByWebId.clear();
96
89
  readyByComposite.clear();
97
90
  for (const pending of pendingByWebId.values()) {
@@ -118,7 +111,7 @@ export function createPodRuntime(options) {
118
111
  };
119
112
  return {
120
113
  open(args) {
121
- const webIdKey = normalizeIdentityPart(args.webId);
114
+ const webIdKey = validateWebId(args.webId);
122
115
  const explicitPodUrl = args.podUrl === undefined ? undefined : normalizePodUrl(args.podUrl);
123
116
  if (explicitPodUrl !== undefined) {
124
117
  const key = compositeKey(args.webId, explicitPodUrl);
@@ -1,22 +1,7 @@
1
- import { normalizeStorageBinding } from './webid-auth.js';
2
- function normalizeIdentity(value) {
3
- const trimmed = value.trim();
4
- try {
5
- return new URL(trimmed).href;
6
- }
7
- catch {
8
- return trimmed;
9
- }
10
- }
11
- function normalizedBinding(binding) {
12
- return {
13
- ...normalizeStorageBinding(binding),
14
- webId: normalizeIdentity(binding.webId),
15
- };
16
- }
1
+ import { normalizeStorageBinding, validateWebId } from './webid-auth.js';
17
2
  export function storageBindingMatches(expected, actual) {
18
- const left = normalizedBinding(expected);
19
- const right = normalizedBinding(actual);
3
+ const left = normalizeStorageBinding(expected);
4
+ const right = normalizeStorageBinding(actual);
20
5
  return left.storageUrl === right.storageUrl && left.webId === right.webId;
21
6
  }
22
7
  export function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], positionalRemembered) {
@@ -38,8 +23,8 @@ export function reconcileStorageSelection(optionsOrWebId, positionalCandidates =
38
23
  if (options.webId === undefined || options.candidates === undefined) {
39
24
  return undefined;
40
25
  }
41
- const webId = normalizeIdentity(options.webId);
42
- const candidates = options.candidates.map(normalizedBinding);
26
+ const webId = validateWebId(options.webId);
27
+ const candidates = options.candidates.map(normalizeStorageBinding);
43
28
  const seenByStorage = new Map();
44
29
  const uniqueCandidates = [];
45
30
  for (const candidate of candidates) {
@@ -57,7 +42,7 @@ export function reconcileStorageSelection(optionsOrWebId, positionalCandidates =
57
42
  }
58
43
  const eligible = uniqueCandidates.filter((candidate) => candidate.webId === webId);
59
44
  if (options.remembered) {
60
- const remembered = normalizedBinding(options.remembered);
45
+ const remembered = normalizeStorageBinding(options.remembered);
61
46
  const exact = eligible.find((candidate) => storageBindingMatches(candidate, remembered));
62
47
  if (exact) {
63
48
  return { status: 'ready', selected: { ...exact } };
@@ -72,6 +72,8 @@ export interface WebIdLoginActions {
72
72
  retry?: (transaction?: WebIdLoginTransaction) => Promise<void> | void;
73
73
  logout(options?: unknown): Promise<void> | void;
74
74
  }
75
+ /** Validate the identifier without rewriting the WebID used for identity equality. */
76
+ export declare function validateWebId(value: string): string;
75
77
  declare function normalizeStorageBinding(binding: StorageBinding): StorageBinding;
76
78
  export declare function normalizeWebIdLoginRoute(input: WebIdLoginRouteDescriptor): WebIdLoginRouteDescriptor;
77
79
  export declare function normalizeApplicationReturnTo(value: string | undefined, allowedPrefixes: readonly string[]): string | undefined;
@@ -35,13 +35,18 @@ function normalizeEndpoint(endpoint, fieldName) {
35
35
  label: endpoint.label,
36
36
  };
37
37
  }
38
+ /** Validate the identifier without rewriting the WebID used for identity equality. */
39
+ export function validateWebId(value) {
40
+ if (typeof value !== 'string' || value === '' || /[\s\u0000-\u001f\u007f-\u009f]/u.test(value)) {
41
+ throw new TypeError('webId must be a non-empty identifier without whitespace or control characters');
42
+ }
43
+ return value;
44
+ }
38
45
  function normalizeStorageBinding(binding) {
39
46
  if (!binding || typeof binding.storageUrl !== 'string' || binding.storageUrl.trim() === '') {
40
47
  throw new TypeError('selectedStorage.storageUrl must be a non-empty absolute URL');
41
48
  }
42
- if (typeof binding.webId !== 'string' || binding.webId.trim() === '') {
43
- throw new TypeError('selectedStorage.webId must be a non-empty identifier URL');
44
- }
49
+ validateWebId(binding.webId);
45
50
  let parsed;
46
51
  try {
47
52
  parsed = new URL(binding.storageUrl.trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
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",
@@ -342,6 +342,12 @@
342
342
  "abort-controller": "^3.0.0",
343
343
  "process": "^0.11.10",
344
344
  "string_decoder": "^1.3.0",
345
+ "@undefineds.co/drizzle-solid": "file:./node_modules/@undefineds.co/drizzle-solid",
346
+ "@undefineds.co/extensions": "file:./node_modules/@undefineds.co/extensions",
347
+ "@undefineds.co/ai-connections": "file:./node_modules/@undefineds.co/ai-connections",
348
+ "@undefineds.co/extension-sdk": "file:./node_modules/@undefineds.co/extension-sdk",
349
+ "@undefineds.co/shared-ui": "file:./node_modules/@undefineds.co/shared-ui",
350
+ "@undefineds.co/solid-sdk": "file:./node_modules/@undefineds.co/solid-sdk",
345
351
  "oidc-provider": "9.5.1",
346
352
  "@inrupt/solid-client-authn-browser": "3.1.1",
347
353
  "jose": "5.10.0",
@@ -370,7 +376,7 @@
370
376
  }
371
377
  },
372
378
  "optionalDependencies": {
373
- "@undefineds.co/xpod-darwin-arm64": "0.4.7"
379
+ "@undefineds.co/xpod-darwin-arm64": "0.4.9"
374
380
  },
375
381
  "resolutions": {
376
382
  "@inrupt/solid-client-authn-browser": "^3.1.1",
@@ -392,6 +398,12 @@
392
398
  },
393
399
  "packageManager": "bun@1.3.12",
394
400
  "bundledDependencies": [
401
+ "@undefineds.co/drizzle-solid",
402
+ "@undefineds.co/extensions",
403
+ "@undefineds.co/ai-connections",
404
+ "@undefineds.co/extension-sdk",
405
+ "@undefineds.co/shared-ui",
406
+ "@undefineds.co/solid-sdk",
395
407
  "@solid/community-server",
396
408
  "oidc-provider",
397
409
  "@undefineds.co/models",