solid-logic 6.0.0 → 6.0.1-1

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 (164) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs.js +1 -1
  2. package/dist/acl/accessControlSubjects.cjs.js +2 -0
  3. package/dist/acl/accessControlSubjects.cjs.js.map +1 -0
  4. package/dist/acl/accessControlSubjects.d.ts +8 -0
  5. package/dist/acl/accessControlSubjects.d.ts.map +1 -0
  6. package/dist/acl/accessControlSubjects.esm.js +75 -0
  7. package/dist/acl/accessControlSubjects.esm.js.map +1 -0
  8. package/dist/acl/aclLogic.cjs.js +1 -1
  9. package/dist/acl/aclLogic.cjs.js.map +1 -1
  10. package/dist/acl/aclLogic.d.ts +10 -0
  11. package/dist/acl/aclLogic.d.ts.map +1 -0
  12. package/dist/acl/aclLogic.esm.js +128 -26
  13. package/dist/acl/aclLogic.esm.js.map +1 -1
  14. package/dist/authSession/authSession.cjs.js.map +1 -1
  15. package/dist/authSession/authSession.d.ts +42 -0
  16. package/dist/authSession/authSession.d.ts.map +1 -0
  17. package/dist/authSession/authSession.esm.js.map +1 -1
  18. package/dist/authSession/events.cjs.js.map +1 -1
  19. package/dist/authSession/events.d.ts +25 -0
  20. package/dist/authSession/events.d.ts.map +1 -0
  21. package/dist/authSession/events.esm.js.map +1 -1
  22. package/dist/authSession/flagAuthorizationOnTransitions.cjs.js +1 -1
  23. package/dist/authSession/flagAuthorizationOnTransitions.cjs.js.map +1 -1
  24. package/dist/authSession/flagAuthorizationOnTransitions.d.ts +76 -0
  25. package/dist/authSession/flagAuthorizationOnTransitions.d.ts.map +1 -0
  26. package/dist/authSession/flagAuthorizationOnTransitions.esm.js +1 -1
  27. package/dist/authSession/flagAuthorizationOnTransitions.esm.js.map +1 -1
  28. package/dist/authSession/identityState.cjs.js +1 -1
  29. package/dist/authSession/identityState.cjs.js.map +1 -1
  30. package/dist/authSession/identityState.d.ts +199 -0
  31. package/dist/authSession/identityState.d.ts.map +1 -0
  32. package/dist/authSession/identityState.esm.js +1 -1
  33. package/dist/authSession/identityState.esm.js.map +1 -1
  34. package/dist/authSession/issuer.d.ts +8 -0
  35. package/dist/authSession/issuer.d.ts.map +1 -0
  36. package/dist/authSession/session.cjs.js +1 -1
  37. package/dist/authSession/session.cjs.js.map +1 -1
  38. package/dist/authSession/session.d.ts +45 -0
  39. package/dist/authSession/session.d.ts.map +1 -0
  40. package/dist/authSession/session.esm.js +1 -1
  41. package/dist/authSession/session.esm.js.map +1 -1
  42. package/dist/authn/SolidAuthnLogic.cjs.js.map +1 -1
  43. package/dist/authn/SolidAuthnLogic.d.ts +61 -0
  44. package/dist/authn/SolidAuthnLogic.d.ts.map +1 -0
  45. package/dist/authn/SolidAuthnLogic.esm.js.map +1 -1
  46. package/dist/authn/authUtil.cjs.js.map +1 -1
  47. package/dist/authn/authUtil.d.ts +17 -0
  48. package/dist/authn/authUtil.d.ts.map +1 -0
  49. package/dist/authn/serverLogout.d.ts +6 -0
  50. package/dist/authn/serverLogout.d.ts.map +1 -0
  51. package/dist/chat/chatLogic.cjs.js +1 -1
  52. package/dist/chat/chatLogic.cjs.js.map +1 -1
  53. package/dist/chat/chatLogic.d.ts +3 -0
  54. package/dist/chat/chatLogic.d.ts.map +1 -0
  55. package/dist/chat/chatLogic.esm.js +5 -5
  56. package/dist/chat/chatLogic.esm.js.map +1 -1
  57. package/dist/directory/catalogDirectory.cjs.js +2 -0
  58. package/dist/directory/catalogDirectory.cjs.js.map +1 -0
  59. package/dist/directory/catalogDirectory.d.ts +5 -0
  60. package/dist/directory/catalogDirectory.d.ts.map +1 -0
  61. package/dist/directory/catalogDirectory.esm.js +54 -0
  62. package/dist/directory/catalogDirectory.esm.js.map +1 -0
  63. package/dist/directory/contactsDirectory.cjs.js +2 -0
  64. package/dist/directory/contactsDirectory.cjs.js.map +1 -0
  65. package/dist/directory/contactsDirectory.d.ts +7 -0
  66. package/dist/directory/contactsDirectory.d.ts.map +1 -0
  67. package/dist/directory/contactsDirectory.esm.js +91 -0
  68. package/dist/directory/contactsDirectory.esm.js.map +1 -0
  69. package/dist/directory/directoryHelpers.cjs.js +2 -0
  70. package/dist/directory/directoryHelpers.cjs.js.map +1 -0
  71. package/dist/directory/directoryHelpers.d.ts +18 -0
  72. package/dist/directory/directoryHelpers.d.ts.map +1 -0
  73. package/dist/directory/directoryHelpers.esm.js +89 -0
  74. package/dist/directory/directoryHelpers.esm.js.map +1 -0
  75. package/dist/directory/directoryLogic.cjs.js +2 -0
  76. package/dist/directory/directoryLogic.cjs.js.map +1 -0
  77. package/dist/directory/directoryLogic.d.ts +6 -0
  78. package/dist/directory/directoryLogic.d.ts.map +1 -0
  79. package/dist/directory/directoryLogic.esm.js +41 -0
  80. package/dist/directory/directoryLogic.esm.js.map +1 -0
  81. package/dist/directory/friendsDirectory.cjs.js +2 -0
  82. package/dist/directory/friendsDirectory.cjs.js.map +1 -0
  83. package/dist/directory/friendsDirectory.d.ts +6 -0
  84. package/dist/directory/friendsDirectory.d.ts.map +1 -0
  85. package/dist/directory/friendsDirectory.esm.js +58 -0
  86. package/dist/directory/friendsDirectory.esm.js.map +1 -0
  87. package/dist/inbox/inboxLogic.d.ts +3 -0
  88. package/dist/inbox/inboxLogic.d.ts.map +1 -0
  89. package/dist/index.cjs.js +1 -1
  90. package/dist/index.cjs.js.map +1 -1
  91. package/dist/index.d.ts +19 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.esm.js +10 -9
  94. package/dist/index.esm.js.map +1 -1
  95. package/dist/issuer/issuerLogic.d.ts +8 -0
  96. package/dist/issuer/issuerLogic.d.ts.map +1 -0
  97. package/dist/logic/CustomError.cjs.js.map +1 -1
  98. package/dist/logic/CustomError.d.ts +21 -0
  99. package/dist/logic/CustomError.d.ts.map +1 -0
  100. package/dist/logic/CustomError.esm.js.map +1 -1
  101. package/dist/logic/solidLogic.cjs.js +1 -1
  102. package/dist/logic/solidLogic.cjs.js.map +1 -1
  103. package/dist/logic/solidLogic.d.ts +6 -0
  104. package/dist/logic/solidLogic.d.ts.map +1 -0
  105. package/dist/logic/solidLogic.esm.js +29 -27
  106. package/dist/logic/solidLogic.esm.js.map +1 -1
  107. package/dist/logic/solidLogicSingleton.cjs.js +1 -1
  108. package/dist/logic/solidLogicSingleton.cjs.js.map +1 -1
  109. package/dist/logic/solidLogicSingleton.d.ts +4 -0
  110. package/dist/logic/solidLogicSingleton.d.ts.map +1 -0
  111. package/dist/logic/solidLogicSingleton.esm.js +2 -1
  112. package/dist/logic/solidLogicSingleton.esm.js.map +1 -1
  113. package/dist/profile/profileAclDocuments.d.ts +2 -0
  114. package/dist/profile/profileAclDocuments.d.ts.map +1 -0
  115. package/dist/profile/profileDocuments.d.ts +2 -0
  116. package/dist/profile/profileDocuments.d.ts.map +1 -0
  117. package/dist/profile/profileLogic.cjs.js.map +1 -1
  118. package/dist/profile/profileLogic.d.ts +3 -0
  119. package/dist/profile/profileLogic.d.ts.map +1 -0
  120. package/dist/profile/profileLogic.esm.js +2 -1
  121. package/dist/profile/profileLogic.esm.js.map +1 -1
  122. package/dist/resource/resourceHttp.d.ts +5 -0
  123. package/dist/resource/resourceHttp.d.ts.map +1 -0
  124. package/dist/resource/resourceLogic.cjs.js +1 -1
  125. package/dist/resource/resourceLogic.cjs.js.map +1 -1
  126. package/dist/resource/resourceLogic.d.ts +4 -0
  127. package/dist/resource/resourceLogic.d.ts.map +1 -0
  128. package/dist/resource/resourceLogic.esm.js +36 -26
  129. package/dist/resource/resourceLogic.esm.js.map +1 -1
  130. package/dist/resource/resourceMetadata.d.ts +5 -0
  131. package/dist/resource/resourceMetadata.d.ts.map +1 -0
  132. package/dist/typeIndex/typeIndexAclDocuments.d.ts +2 -0
  133. package/dist/typeIndex/typeIndexAclDocuments.d.ts.map +1 -0
  134. package/dist/typeIndex/typeIndexDocuments.d.ts +3 -0
  135. package/dist/typeIndex/typeIndexDocuments.d.ts.map +1 -0
  136. package/dist/typeIndex/typeIndexLogic.cjs.js.map +1 -1
  137. package/dist/typeIndex/typeIndexLogic.d.ts +3 -0
  138. package/dist/typeIndex/typeIndexLogic.d.ts.map +1 -0
  139. package/dist/typeIndex/typeIndexLogic.esm.js +10 -5
  140. package/dist/typeIndex/typeIndexLogic.esm.js.map +1 -1
  141. package/dist/types.d.ts +192 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/util/containerLogic.cjs.js.map +1 -1
  144. package/dist/util/containerLogic.d.ts +12 -0
  145. package/dist/util/containerLogic.d.ts.map +1 -0
  146. package/dist/util/containerLogic.esm.js.map +1 -1
  147. package/dist/util/debug.d.ts +5 -0
  148. package/dist/util/debug.d.ts.map +1 -0
  149. package/dist/util/ns.cjs.js.map +1 -1
  150. package/dist/util/ns.d.ts +2 -0
  151. package/dist/util/ns.d.ts.map +1 -0
  152. package/dist/util/utilityLogic.cjs.js +1 -1
  153. package/dist/util/utilityLogic.cjs.js.map +1 -1
  154. package/dist/util/utilityLogic.d.ts +16 -0
  155. package/dist/util/utilityLogic.d.ts.map +1 -0
  156. package/dist/util/utilityLogic.esm.js +6 -2
  157. package/dist/util/utilityLogic.esm.js.map +1 -1
  158. package/dist/util/utils.cjs.js +1 -1
  159. package/dist/util/utils.cjs.js.map +1 -1
  160. package/dist/util/utils.d.ts +8 -0
  161. package/dist/util/utils.d.ts.map +1 -0
  162. package/dist/util/utils.esm.js +5 -3
  163. package/dist/util/utils.esm.js.map +1 -1
  164. package/package.json +6 -4
@@ -1 +1 @@
1
- {"version":3,"file":"SolidAuthnLogic.cjs.js","names":[],"sources":["../../src/authn/SolidAuthnLogic.ts"],"sourcesContent":["import { namedNode, NamedNode, sym } from 'rdflib'\nimport { appContext, offlineTestID } from './authUtil'\nimport * as debug from '../util/debug'\nimport {\n effectiveIdentity,\n restoreSession,\n sessionOwnsIdentity,\n sessionWasCleared,\n subscribeIdentity,\n type IdentitySubscription,\n type SessionLike\n} from '../authSession/identityState'\nimport type { SessionWithLegacyEvents } from '../authSession/authSession'\nimport type { AuthenticationContext, AuthnLogic } from '../types'\n\n// Some auth clients (uvdsl worker-backed session) only settle restore() on\n// a worker message; a missing/unreachable RefreshWorker asset makes it hang\n// forever. This caps the wait so the login UI can never spin indefinitely.\nconst SESSION_RESTORE_TIMEOUT_MS = 5000\n\n/**\n * Await a session restore promise, but give up after\n * SESSION_RESTORE_TIMEOUT_MS and resolve with undefined so callers can\n * treat a stalled restore as \"no previous session\".\n */\nasync function withRestoreTimeout<T> (promise: Promise<T> | null): Promise<T | undefined> {\n if (promise === null) return undefined\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<undefined>(resolve => {\n timer = setTimeout(() => resolve(undefined), SESSION_RESTORE_TIMEOUT_MS)\n })\n try {\n return await Promise.race([promise, timeout])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\nexport class SolidAuthnLogic implements AuthnLogic {\n private session: SessionWithLegacyEvents\n private checkUserInFlight: Promise<NamedNode | null> | null = null\n private sessionRestoreHookAttached = false\n /**\n * This instance's subscription to the session's identity state. Its lifetime\n * IS this instance's: a cookie probe that answers after `dispose()` is\n * ignored, and the refocus listener is removed with the last instance using\n * the session (see identityState.ts).\n */\n private identity: IdentitySubscription\n\n constructor (solidAuthSession: SessionWithLegacyEvents) {\n this.session = solidAuthSession\n // The cookie-backed identity is invisible to the session (it stays inactive\n // and WebID-less), so it is re-probed when the tab regains focus: another\n // tab may have logged out or switched identity while this one was\n // backgrounded. Only meaningful where the probe applies (*.localhost NSS).\n //\n // TEMPORARY, pending the uvdsl change: this subscription exists because the\n // library does not report a cookie identity change (nor a WebID change that\n // keeps the session active). See the identityState header.\n this.identity = subscribeIdentity(solidAuthSession as unknown as SessionLike, {\n onRefocus: () => this.refreshCookieBackedFallback()\n })\n }\n\n /**\n * Detaches this instance from the session's identity state: the refocus\n * listener is removed with the last instance using the session, and a probe\n * that is still in flight can no longer apply its result.\n */\n dispose (): void {\n this.identity.unsubscribe()\n }\n\n /**\n * Re-probes the NSS cookie-backed identity. Skipped while the session owns\n * the identity — the probe only exists for the case where it does not — and\n * the state drops the result if the session takes ownership while the probe\n * is in flight.\n */\n private async refreshCookieBackedFallback (): Promise<void> {\n if (sessionOwnsIdentity(this.session as unknown as SessionLike)) return\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n }\n\n // we created authSession getter because we want to access it as authn.authSession externally\n get authSession (): SessionWithLegacyEvents { return this.session }\n\n currentUser (): NamedNode | null {\n const app = appContext()\n if (app.viewingNoAuthPage) {\n return sym(app.webId)\n }\n // The state answers with the session's identity when it owns one, the\n // probed cookie identity when the session is inactive (or cleared), and\n // nothing when neither does — a logout that retains the cached WebID must\n // not keep answering for the previous user.\n const { webId } = effectiveIdentity(this.session as unknown as SessionLike)\n return webId ? sym(webId) : offlineTestID() // null unless testing\n }\n\n /**\n * Retrieves currently logged in webId from either\n * defaultTestUser or SolidAuth\n * Also activates a session after login\n * @param [setUserCallback] Optional callback\n * @returns Resolves with webId uri, if no callback provided\n */\n async checkUser<T> (\n setUserCallback?: (me: NamedNode | null) => T\n ): Promise<NamedNode | T | null> {\n // Save hash for \"restorePreviousSession\"\n const preLoginRedirectHash = new URL(window.location.href).hash\n if (preLoginRedirectHash) {\n window.localStorage.setItem('preLoginRedirectHash', preLoginRedirectHash)\n }\n const sessionAny = this.session as any\n if (!this.sessionRestoreHookAttached && typeof sessionAny?.events?.on === 'function') {\n // Backward-compatible hook for auth clients exposing an EventEmitter-style API.\n sessionAny.events.on('sessionRestore', (url: string) => {\n debug.log(`Session restored to ${url}`)\n if (document.location.toString() !== url) history.replaceState(null, '', url)\n })\n this.sessionRestoreHookAttached = true\n }\n\n if (!this.checkUserInFlight) {\n this.checkUserInFlight = this.resolveCurrentUser()\n }\n\n const inFlight = this.checkUserInFlight\n let me: NamedNode | null\n try {\n me = await inFlight\n } finally {\n if (this.checkUserInFlight === inFlight) {\n this.checkUserInFlight = null\n }\n }\n\n return Promise.resolve(setUserCallback ? setUserCallback(me) : me)\n }\n\n private async resolveCurrentUser (): Promise<NamedNode | null> {\n const sessionAny = this.session as any\n\n /**\n * Handle a successful authentication redirect\n */\n const redirectUrl = new URL(window.location.href)\n redirectUrl.hash = ''\n if (typeof sessionAny?.handleIncomingRedirect === 'function') {\n await sessionAny.handleIncomingRedirect({\n restorePreviousSession: true,\n url: redirectUrl.href\n })\n } else {\n // uvdsl-style session (no handleIncomingRedirect): restore then handle redirect.\n //\n // The worker-backed session (WebWorkerSession) resolves restore() ONLY\n // when the SharedWorker posts a message back. If the worker asset can't\n // be fetched — local/dev servers that don't serve the RefreshWorker\n // chunk at the resolved URL, wrong MIME type, CSP, or a worker that\n // fails before `onconnect` — the promise never settles and the login\n // UI would spin forever. Race it against a timeout and treat a stall\n // as \"no previous session\" so the page can render the login button.\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n // The shared restore lock also covers this call: a refocus resync can be\n // in flight at the same time, and two overlapping restores could write an\n // older identity back over a newer one.\n const restoring = restoreSession(sessionAny)\n if (restoring) {\n try {\n await withRestoreTimeout(restoring)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n // A failed restore on an inactive session just means \"no usable\n // session to restore\" — whether that's \"No session to restore.\",\n // a stale refresh token / dead client_id returning HTTP 400, or a\n // missing session database. Never let it block the login UI: log\n // and continue as logged-out so the page renders the login button.\n // Only re-throw when the session actually became active, which is\n // an unexpected refresh failure worth surfacing.\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (isNowActive && !/no session to restore/i.test(message)) {\n throw error\n }\n debug.log(`Session restore failed, continuing logged-out: ${message}`)\n }\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('sessionRestore', window.location.href)\n }\n }\n if (typeof sessionAny?.handleRedirectFromLogin === 'function') {\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n await sessionAny.handleRedirectFromLogin()\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('login')\n }\n }\n }\n\n // Check to see if a hash was stored in local storage\n const postLoginRedirectHash = window.localStorage.getItem('preLoginRedirectHash')\n if (postLoginRedirectHash) {\n const curUrl = new URL(window.location.href)\n if (curUrl.hash !== postLoginRedirectHash) {\n if (history.pushState) {\n // debug.log('Setting window.location.has using pushState')\n history.pushState(null, document.title, postLoginRedirectHash)\n } else {\n // debug.warn('Setting window.location.has using location.hash')\n location.hash = postLoginRedirectHash\n }\n curUrl.hash = postLoginRedirectHash\n }\n // See https://stackoverflow.com/questions/3870057/how-can-i-update-window-location-hash-without-jumping-the-document\n // window.location.href = curUrl.toString()// @@ See https://developer.mozilla.org/en-US/docs/Web/API/Window/location\n window.localStorage.setItem('preLoginRedirectHash', '')\n }\n\n // Check to see if already logged in / have the WebID\n let me = offlineTestID()\n if (me) {\n return me\n }\n\n let webId = this.webIdFromSession(sessionAny?.info, sessionAny)\n if (!webId) {\n // NSS-specific fallback: recover the WebID from the NSS cookie session\n // when the client restore is empty. The result goes through the identity\n // state, which drops it if the session took ownership while the probe was\n // in flight (or if this instance was disposed meanwhile) — and reports the\n // change like any other transition.\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n webId = effectiveIdentity(sessionAny).webId ?? null\n }\n\n if (webId) {\n me = this.saveUser(webId)\n }\n\n if (me) {\n debug.log(`(Logged in as ${me} by authentication)`)\n }\n\n return me\n }\n\n private async probeNssCookieBackedWebId (): Promise<string | null> {\n if (typeof window === 'undefined') {\n return null\n }\n\n const { hostname, port, protocol } = window.location\n const localhostSuffix = '.localhost'\n // NSS local pods use subdomains like alice.localhost.\n if (!hostname.endsWith(localhostSuffix)) {\n return null\n }\n\n const podName = hostname.slice(0, -localhostSuffix.length)\n if (!podName || podName === 'localhost' || podName.includes('.')) {\n return null\n }\n\n try {\n // NSS returns 403 on this account page when the cookie session is valid.\n const probeResponse = await fetch('/account/password/change', {\n credentials: 'include',\n redirect: 'manual',\n cache: 'no-store'\n })\n if (probeResponse.status !== 403) {\n return null\n }\n const origin = `${protocol}//${hostname}${port ? `:${port}` : ''}`\n return `${origin}/profile/card#me`\n } catch (_error) {\n return null\n }\n }\n\n /**\n * Saves `webId` in `context.me`\n * @param webId\n * @param context\n *\n * @returns Returns the WebID, after setting it\n */\n saveUser (\n webId: NamedNode | string | null,\n context?: AuthenticationContext\n ): NamedNode | null {\n let webIdUri: string\n if (webId) {\n webIdUri = (typeof webId === 'string') ? webId : webId.uri\n const me = namedNode(webIdUri)\n if (context) {\n context.me = me\n }\n return me\n }\n return null\n }\n\n /**\n * @returns {Promise<string|null>} Resolves with WebID URI or null\n */\n webIdFromSession (\n sessionInfo?: { webId?: string, isLoggedIn?: boolean },\n sessionRoot?: { webId?: string, isLoggedIn?: boolean, isActive?: boolean }\n ): string | null {\n const webId = sessionInfo?.webId || sessionRoot?.webId\n if (!webId) {\n return null\n }\n const infoLoggedIn = sessionInfo?.isLoggedIn\n const rootLoggedIn = sessionRoot?.isLoggedIn\n const rootActive = sessionRoot?.isActive\n // An explicit inactive/not-logged-in flag wins over a cached WebID and\n // over a positive flag in another source — the same rule the identity\n // state uses (see identityState.ts). The session root has no `isLoggedIn`\n // property, so requiring every source to be false kept a cached WebID\n // alive across a logout; a mixed snapshot must not resurrect one either. A\n // session whose backing store lost it is inactive as well, however\n // positive its own fields still look.\n if (sessionWasCleared(sessionRoot) ||\n infoLoggedIn === false || rootLoggedIn === false || rootActive === false) {\n return null\n }\n // Active, or a legacy session that reports no state at all.\n return webId\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SolidAuthnLogic.cjs.js","names":[],"sources":["../../src/authn/SolidAuthnLogic.ts"],"sourcesContent":["import { namedNode, NamedNode, sym } from 'rdflib'\nimport { appContext, offlineTestID } from './authUtil'\nimport * as debug from '../util/debug'\nimport {\n effectiveIdentity,\n restoreSession,\n sessionOwnsIdentity,\n sessionWasCleared,\n subscribeIdentity,\n type IdentitySubscription,\n type SessionLike\n} from '../authSession/identityState'\nimport type { SessionWithLegacyEvents } from '../authSession/authSession'\nimport type { AuthenticationContext, AuthnLogic } from '../types'\n\n// Some auth clients (uvdsl worker-backed session) only settle restore() on\n// a worker message; a missing/unreachable RefreshWorker asset makes it hang\n// forever. This caps the wait so the login UI can never spin indefinitely.\nconst SESSION_RESTORE_TIMEOUT_MS = 5000\n\n/**\n * Await a session restore promise, but give up after\n * SESSION_RESTORE_TIMEOUT_MS and resolve with undefined so callers can\n * treat a stalled restore as \"no previous session\".\n */\nasync function withRestoreTimeout<T> (promise: Promise<T> | null): Promise<T | undefined> {\n if (promise === null) return undefined\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<undefined>(resolve => {\n timer = setTimeout(() => resolve(undefined), SESSION_RESTORE_TIMEOUT_MS)\n })\n try {\n return await Promise.race([promise, timeout])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\nexport class SolidAuthnLogic implements AuthnLogic {\n private session: SessionWithLegacyEvents\n private checkUserInFlight: Promise<NamedNode | null> | null = null\n private sessionRestoreHookAttached = false\n /**\n * This instance's subscription to the session's identity state. Its lifetime\n * IS this instance's: a cookie probe that answers after `dispose()` is\n * ignored, and the refocus listener is removed with the last instance using\n * the session (see identityState.ts).\n */\n private identity: IdentitySubscription\n\n constructor (solidAuthSession: SessionWithLegacyEvents) {\n this.session = solidAuthSession\n // The cookie-backed identity is invisible to the session (it stays inactive\n // and WebID-less), so it is re-probed when the tab regains focus: another\n // tab may have logged out or switched identity while this one was\n // backgrounded. Only meaningful where the probe applies (*.localhost NSS).\n //\n // TEMPORARY, pending the uvdsl change: this subscription exists because the\n // library does not report a cookie identity change (nor a WebID change that\n // keeps the session active). See the identityState header.\n this.identity = subscribeIdentity(solidAuthSession as unknown as SessionLike, {\n onRefocus: () => this.refreshCookieBackedFallback()\n })\n }\n\n /**\n * Detaches this instance from the session's identity state: the refocus\n * listener is removed with the last instance using the session, and a probe\n * that is still in flight can no longer apply its result.\n */\n dispose (): void {\n this.identity.unsubscribe()\n }\n\n /**\n * Re-probes the NSS cookie-backed identity. Skipped while the session owns\n * the identity — the probe only exists for the case where it does not — and\n * the state drops the result if the session takes ownership while the probe\n * is in flight.\n */\n private async refreshCookieBackedFallback (): Promise<void> {\n if (sessionOwnsIdentity(this.session as unknown as SessionLike)) return\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n }\n\n // we created authSession getter because we want to access it as authn.authSession externally\n get authSession (): SessionWithLegacyEvents { return this.session }\n\n currentUser (): NamedNode | null {\n const app = appContext()\n if (app.viewingNoAuthPage) {\n return sym(app.webId)\n }\n // The state answers with the session's identity when it owns one, the\n // probed cookie identity when the session is inactive (or cleared), and\n // nothing when neither does — a logout that retains the cached WebID must\n // not keep answering for the previous user.\n const { webId } = effectiveIdentity(this.session as unknown as SessionLike)\n return webId ? sym(webId) : offlineTestID() // null unless testing\n }\n\n /**\n * Retrieves currently logged in webId from either\n * defaultTestUser or SolidAuth\n * Also activates a session after login\n * @param [setUserCallback] Optional callback\n * @returns Resolves with webId uri, if no callback provided\n */\n async checkUser<T> (\n setUserCallback?: (me: NamedNode | null) => T\n ): Promise<NamedNode | T | null> {\n // Save hash for \"restorePreviousSession\"\n const preLoginRedirectHash = new URL(window.location.href).hash\n if (preLoginRedirectHash) {\n window.localStorage.setItem('preLoginRedirectHash', preLoginRedirectHash)\n }\n const sessionAny = this.session as any\n if (!this.sessionRestoreHookAttached && typeof sessionAny?.events?.on === 'function') {\n // Backward-compatible hook for auth clients exposing an EventEmitter-style API.\n sessionAny.events.on('sessionRestore', (url: string) => {\n debug.log(`Session restored to ${url}`)\n if (document.location.toString() !== url) history.replaceState(null, '', url)\n })\n this.sessionRestoreHookAttached = true\n }\n\n if (!this.checkUserInFlight) {\n this.checkUserInFlight = this.resolveCurrentUser()\n }\n\n const inFlight = this.checkUserInFlight\n let me: NamedNode | null\n try {\n me = await inFlight\n } finally {\n if (this.checkUserInFlight === inFlight) {\n this.checkUserInFlight = null\n }\n }\n\n return Promise.resolve(setUserCallback ? setUserCallback(me) : me)\n }\n\n private async resolveCurrentUser (): Promise<NamedNode | null> {\n const sessionAny = this.session as any\n\n /**\n * Handle a successful authentication redirect\n */\n const redirectUrl = new URL(window.location.href)\n redirectUrl.hash = ''\n if (typeof sessionAny?.handleIncomingRedirect === 'function') {\n await sessionAny.handleIncomingRedirect({\n restorePreviousSession: true,\n url: redirectUrl.href\n })\n } else {\n // uvdsl-style session (no handleIncomingRedirect): restore then handle redirect.\n //\n // The worker-backed session (WebWorkerSession) resolves restore() ONLY\n // when the SharedWorker posts a message back. If the worker asset can't\n // be fetched — local/dev servers that don't serve the RefreshWorker\n // chunk at the resolved URL, wrong MIME type, CSP, or a worker that\n // fails before `onconnect` — the promise never settles and the login\n // UI would spin forever. Race it against a timeout and treat a stall\n // as \"no previous session\" so the page can render the login button.\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n // The shared restore lock also covers this call: a refocus resync can be\n // in flight at the same time, and two overlapping restores could write an\n // older identity back over a newer one.\n const restoring = restoreSession(sessionAny)\n if (restoring) {\n try {\n await withRestoreTimeout(restoring)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n // A failed restore on an inactive session just means \"no usable\n // session to restore\" — whether that's \"No session to restore.\",\n // a stale refresh token / dead client_id returning HTTP 400, or a\n // missing session database. Never let it block the login UI: log\n // and continue as logged-out so the page renders the login button.\n // Only re-throw when the session actually became active, which is\n // an unexpected refresh failure worth surfacing.\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (isNowActive && !/no session to restore/i.test(message)) {\n throw error\n }\n debug.log(`Session restore failed, continuing logged-out: ${message}`)\n }\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('sessionRestore', window.location.href)\n }\n }\n if (typeof sessionAny?.handleRedirectFromLogin === 'function') {\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n await sessionAny.handleRedirectFromLogin()\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('login')\n }\n }\n }\n\n // Check to see if a hash was stored in local storage\n const postLoginRedirectHash = window.localStorage.getItem('preLoginRedirectHash')\n if (postLoginRedirectHash) {\n const curUrl = new URL(window.location.href)\n if (curUrl.hash !== postLoginRedirectHash) {\n if (history.pushState) {\n // debug.log('Setting window.location.has using pushState')\n history.pushState(null, document.title, postLoginRedirectHash)\n } else {\n // debug.warn('Setting window.location.has using location.hash')\n location.hash = postLoginRedirectHash\n }\n curUrl.hash = postLoginRedirectHash\n }\n // See https://stackoverflow.com/questions/3870057/how-can-i-update-window-location-hash-without-jumping-the-document\n // window.location.href = curUrl.toString()// @@ See https://developer.mozilla.org/en-US/docs/Web/API/Window/location\n window.localStorage.setItem('preLoginRedirectHash', '')\n }\n\n // Check to see if already logged in / have the WebID\n let me = offlineTestID()\n if (me) {\n return me\n }\n\n let webId = this.webIdFromSession(sessionAny?.info, sessionAny)\n if (!webId) {\n // NSS-specific fallback: recover the WebID from the NSS cookie session\n // when the client restore is empty. The result goes through the identity\n // state, which drops it if the session took ownership while the probe was\n // in flight (or if this instance was disposed meanwhile) — and reports the\n // change like any other transition.\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n webId = effectiveIdentity(sessionAny).webId ?? null\n }\n\n if (webId) {\n me = this.saveUser(webId)\n }\n\n if (me) {\n debug.log(`(Logged in as ${me} by authentication)`)\n }\n\n return me\n }\n\n private async probeNssCookieBackedWebId (): Promise<string | null> {\n if (typeof window === 'undefined') {\n return null\n }\n\n const { hostname, port, protocol } = window.location\n const localhostSuffix = '.localhost'\n // NSS local pods use subdomains like alice.localhost.\n if (!hostname.endsWith(localhostSuffix)) {\n return null\n }\n\n const podName = hostname.slice(0, -localhostSuffix.length)\n if (!podName || podName === 'localhost' || podName.includes('.')) {\n return null\n }\n\n try {\n // NSS returns 403 on this account page when the cookie session is valid.\n const probeResponse = await fetch('/account/password/change', {\n credentials: 'include',\n redirect: 'manual',\n cache: 'no-store'\n })\n if (probeResponse.status !== 403) {\n return null\n }\n const origin = `${protocol}//${hostname}${port ? `:${port}` : ''}`\n return `${origin}/profile/card#me`\n } catch (_error) {\n return null\n }\n }\n\n /**\n * Saves `webId` in `context.me`\n * @param webId\n * @param context\n *\n * @returns Returns the WebID, after setting it\n */\n saveUser (\n webId: NamedNode | string | null,\n context?: AuthenticationContext\n ): NamedNode | null {\n let webIdUri: string\n if (webId) {\n webIdUri = (typeof webId === 'string') ? webId : webId.uri\n const me = namedNode(webIdUri)\n if (context) {\n context.me = me\n }\n return me\n }\n return null\n }\n\n /**\n * @returns {Promise<string|null>} Resolves with WebID URI or null\n */\n webIdFromSession (\n sessionInfo?: { webId?: string, isLoggedIn?: boolean },\n sessionRoot?: { webId?: string, isLoggedIn?: boolean, isActive?: boolean }\n ): string | null {\n const webId = sessionInfo?.webId || sessionRoot?.webId\n if (!webId) {\n return null\n }\n const infoLoggedIn = sessionInfo?.isLoggedIn\n const rootLoggedIn = sessionRoot?.isLoggedIn\n const rootActive = sessionRoot?.isActive\n // An explicit inactive/not-logged-in flag wins over a cached WebID and\n // over a positive flag in another source — the same rule the identity\n // state uses (see identityState.ts). The session root has no `isLoggedIn`\n // property, so requiring every source to be false kept a cached WebID\n // alive across a logout; a mixed snapshot must not resurrect one either. A\n // session whose backing store lost it is inactive as well, however\n // positive its own fields still look.\n if (sessionWasCleared(sessionRoot) ||\n infoLoggedIn === false || rootLoggedIn === false || rootActive === false) {\n return null\n }\n // Active, or a legacy session that reports no state at all.\n return webId\n }\n}\n"],"mappings":"gJAkBA,IAAM,EAA6B,IAOnC,eAAe,EAAuB,EAAoD,CACxF,GAAI,IAAY,KAAM,OACtB,IAAI,EACE,EAAU,IAAI,QAAmB,GAAW,CAChD,EAAQ,eAAiB,EAAQ,IAAA,EAAS,EAAG,CAA0B,CACzE,CAAC,EACD,GAAI,CACF,OAAO,MAAM,QAAQ,KAAK,CAAC,EAAS,CAAO,CAAC,CAC9C,QAAU,CACJ,IAAU,IAAA,IAAW,aAAa,CAAK,CAC7C,CACF,CAEA,IAAa,EAAb,KAAmD,CAYjD,YAAa,EAA2C,CAVM,KAAA,kBAAA,KACzB,KAAA,2BAAA,GAUnC,KAAK,QAAU,EASf,KAAK,SAAW,EAAA,kBAAkB,EAA4C,CAC5E,cAAiB,KAAK,4BAA4B,CACpD,CAAC,CACH,CAOA,SAAiB,CACf,KAAK,SAAS,YAAY,CAC5B,CAQA,MAAc,6BAA8C,CACtD,EAAA,oBAAoB,KAAK,OAAiC,GAC9D,KAAK,SAAS,qBAAqB,MAAM,KAAK,0BAA0B,CAAC,CAC3E,CAGA,IAAI,aAAwC,CAAE,OAAO,KAAK,OAAQ,CAElE,aAAiC,CAC/B,IAAM,EAAM,EAAA,WAAW,EACvB,GAAI,EAAI,kBACN,OAAA,EAAO,EAAA,IAAA,CAAI,EAAI,KAAK,EAMtB,GAAM,CAAE,SAAU,EAAA,kBAAkB,KAAK,OAAiC,EAC1E,OAAO,GAAA,EAAQ,EAAA,IAAA,CAAI,CAAK,EAAI,EAAA,cAAc,CAC5C,CASA,MAAM,UACJ,EAC+B,CAE/B,IAAM,EAAuB,IAAI,IAAI,OAAO,SAAS,IAAI,CAAC,CAAC,KACvD,GACF,OAAO,aAAa,QAAQ,uBAAwB,CAAoB,EAE1E,IAAM,EAAa,KAAK,QACpB,CAAC,KAAK,4BAA8B,OAAO,GAAY,QAAQ,IAAO,aAExE,EAAW,OAAO,GAAG,iBAAmB,GAAgB,CACtD,EAAA,IAAU,uBAAuB,GAAK,EAClC,SAAS,SAAS,SAAS,IAAM,GAAK,QAAQ,aAAa,KAAM,GAAI,CAAG,CAC9E,CAAC,EACD,KAAK,2BAA6B,IAGpC,AACE,KAAK,oBAAoB,KAAK,mBAAmB,EAGnD,IAAM,EAAW,KAAK,kBAClB,EACJ,GAAI,CACF,EAAK,MAAM,CACb,QAAU,CACJ,KAAK,oBAAsB,IAC7B,KAAK,kBAAoB,KAE7B,CAEA,OAAO,QAAQ,QAAQ,EAAkB,EAAgB,CAAE,EAAI,CAAE,CACnE,CAEA,MAAc,oBAAiD,CAC7D,IAAM,EAAa,KAAK,QAKlB,EAAc,IAAI,IAAI,OAAO,SAAS,IAAI,EAEhD,GADA,EAAY,KAAO,GACf,OAAO,GAAY,wBAA2B,WAChD,MAAM,EAAW,uBAAuB,CACtC,uBAAwB,GACxB,IAAK,EAAY,IACnB,CAAC,MACI,CAUL,IAAM,EAAY,GAAY,UAAY,EAAQ,GAAY,MAIxD,EAAY,EAAA,eAAe,CAAU,EAC3C,GAAI,EAAW,CACb,GAAI,CACF,MAAM,EAAmB,CAAS,CACpC,OAAS,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EASrE,IADoB,GAAY,UAAY,EAAQ,GAAY,QAC7C,CAAC,yBAAyB,KAAK,CAAO,EACvD,MAAM,EAER,EAAA,IAAU,kDAAkD,GAAS,CACvE,CACA,IAAM,EAAc,GAAY,UAAY,EAAQ,GAAY,MAC5D,CAAC,GAAa,GAChB,EAAW,QAAQ,KAAK,iBAAkB,OAAO,SAAS,IAAI,CAElE,CACA,GAAI,OAAO,GAAY,yBAA4B,WAAY,CAC7D,IAAM,EAAY,GAAY,UAAY,EAAQ,GAAY,MAC9D,MAAM,EAAW,wBAAwB,EACzC,IAAM,EAAc,GAAY,UAAY,EAAQ,GAAY,MAC5D,CAAC,GAAa,GAChB,EAAW,QAAQ,KAAK,OAAO,CAEnC,CACF,CAGA,IAAM,EAAwB,OAAO,aAAa,QAAQ,sBAAsB,EAChF,GAAI,EAAuB,CACzB,IAAM,EAAS,IAAI,IAAI,OAAO,SAAS,IAAI,EACvC,EAAO,OAAS,IACd,QAAQ,UAEV,QAAQ,UAAU,KAAM,SAAS,MAAO,CAAqB,EAG7D,SAAS,KAAO,EAElB,EAAO,KAAO,GAIhB,OAAO,aAAa,QAAQ,uBAAwB,EAAE,CACxD,CAGA,IAAI,EAAK,EAAA,cAAc,EACvB,GAAI,EACF,OAAO,EAGT,IAAI,EAAQ,KAAK,iBAAiB,GAAY,KAAM,CAAU,EAmB9D,MAlBA,CAOE,KADA,KAAK,SAAS,qBAAqB,MAAM,KAAK,0BAA0B,CAAC,EACjE,EAAA,kBAAkB,CAAU,CAAC,CAAC,OAAS,MAG7C,IACF,EAAK,KAAK,SAAS,CAAK,GAGtB,GACF,EAAA,IAAU,iBAAiB,EAAG,oBAAoB,EAG7C,CACT,CAEA,MAAc,2BAAqD,CACjE,GAAI,OAAO,OAAW,IACpB,OAAO,KAGT,GAAM,CAAE,WAAU,OAAM,YAAa,OAAO,SAG5C,GAAI,CAAC,EAAS,SAAS,YAAe,EACpC,OAAO,KAGT,IAAM,EAAU,EAAS,MAAM,EAAG,GAAuB,EACzD,GAAI,CAAC,GAAW,IAAY,aAAe,EAAQ,SAAS,GAAG,EAC7D,OAAO,KAGT,GAAI,CAWF,OAJI,MALwB,MAAM,2BAA4B,CAC5D,YAAa,UACb,SAAU,SACV,MAAO,UACT,CAAC,EAAA,CACiB,SAAW,IAItB,GAAG,GADQ,EAAS,IAAI,IAAW,EAAO,IAAI,IAAS,KAC7C,kBAHR,IAIX,MAAiB,CACf,OAAO,IACT,CACF,CASA,SACE,EACA,EACkB,CAClB,IAAI,EACJ,GAAI,EAAO,CACT,EAAY,OAAO,GAAU,SAAY,EAAQ,EAAM,IACvD,IAAM,GAAA,EAAK,EAAA,UAAA,CAAU,CAAQ,EAI7B,OAHI,IACF,EAAQ,GAAK,GAER,CACT,CACA,OAAO,IACT,CAKA,iBACE,EACA,EACe,CACf,IAAM,EAAQ,GAAa,OAAS,GAAa,MACjD,GAAI,CAAC,EACH,OAAO,KAET,IAAM,EAAe,GAAa,WAC5B,EAAe,GAAa,WAC5B,EAAa,GAAa,SAahC,OALI,EAAA,kBAAkB,CAAW,GAC/B,IAAiB,IAAS,IAAiB,IAAS,IAAe,GAC5D,KAGF,CACT,CACF"}
@@ -0,0 +1,61 @@
1
+ import { NamedNode } from 'rdflib';
2
+ import type { SessionWithLegacyEvents } from '../authSession/authSession';
3
+ import type { AuthenticationContext, AuthnLogic } from '../types';
4
+ export declare class SolidAuthnLogic implements AuthnLogic {
5
+ private session;
6
+ private checkUserInFlight;
7
+ private sessionRestoreHookAttached;
8
+ /**
9
+ * This instance's subscription to the session's identity state. Its lifetime
10
+ * IS this instance's: a cookie probe that answers after `dispose()` is
11
+ * ignored, and the refocus listener is removed with the last instance using
12
+ * the session (see identityState.ts).
13
+ */
14
+ private identity;
15
+ constructor(solidAuthSession: SessionWithLegacyEvents);
16
+ /**
17
+ * Detaches this instance from the session's identity state: the refocus
18
+ * listener is removed with the last instance using the session, and a probe
19
+ * that is still in flight can no longer apply its result.
20
+ */
21
+ dispose(): void;
22
+ /**
23
+ * Re-probes the NSS cookie-backed identity. Skipped while the session owns
24
+ * the identity — the probe only exists for the case where it does not — and
25
+ * the state drops the result if the session takes ownership while the probe
26
+ * is in flight.
27
+ */
28
+ private refreshCookieBackedFallback;
29
+ get authSession(): SessionWithLegacyEvents;
30
+ currentUser(): NamedNode | null;
31
+ /**
32
+ * Retrieves currently logged in webId from either
33
+ * defaultTestUser or SolidAuth
34
+ * Also activates a session after login
35
+ * @param [setUserCallback] Optional callback
36
+ * @returns Resolves with webId uri, if no callback provided
37
+ */
38
+ checkUser<T>(setUserCallback?: (me: NamedNode | null) => T): Promise<NamedNode | T | null>;
39
+ private resolveCurrentUser;
40
+ private probeNssCookieBackedWebId;
41
+ /**
42
+ * Saves `webId` in `context.me`
43
+ * @param webId
44
+ * @param context
45
+ *
46
+ * @returns Returns the WebID, after setting it
47
+ */
48
+ saveUser(webId: NamedNode | string | null, context?: AuthenticationContext): NamedNode | null;
49
+ /**
50
+ * @returns {Promise<string|null>} Resolves with WebID URI or null
51
+ */
52
+ webIdFromSession(sessionInfo?: {
53
+ webId?: string;
54
+ isLoggedIn?: boolean;
55
+ }, sessionRoot?: {
56
+ webId?: string;
57
+ isLoggedIn?: boolean;
58
+ isActive?: boolean;
59
+ }): string | null;
60
+ }
61
+ //# sourceMappingURL=SolidAuthnLogic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SolidAuthnLogic.d.ts","sourceRoot":"","sources":["../../src/authn/SolidAuthnLogic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAO,MAAM,QAAQ,CAAA;AAYlD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAyBjE,qBAAa,eAAgB,YAAW,UAAU;IAChD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,0BAA0B,CAAQ;IAC1C;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAsB;gBAEzB,gBAAgB,EAAE,uBAAuB;IAetD;;;;OAIG;IACH,OAAO,IAAK,IAAI;IAIhB;;;;;OAKG;YACW,2BAA2B;IAMzC,IAAI,WAAW,IAAK,uBAAuB,CAAwB;IAEnE,WAAW,IAAK,SAAS,GAAG,IAAI;IAahC;;;;;;OAMG;IACG,SAAS,CAAC,CAAC,EACf,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,KAAK,CAAC,GAC5C,OAAO,CAAC,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC;YAiClB,kBAAkB;YA4GlB,yBAAyB;IAkCvC;;;;;;OAMG;IACH,QAAQ,CACN,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,SAAS,GAAG,IAAI;IAanB;;OAEG;IACH,gBAAgB,CACd,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,EACtD,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GACzE,MAAM,GAAG,IAAI;CAsBjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"SolidAuthnLogic.esm.js","names":[],"sources":["../../src/authn/SolidAuthnLogic.ts"],"sourcesContent":["import { namedNode, NamedNode, sym } from 'rdflib'\nimport { appContext, offlineTestID } from './authUtil'\nimport * as debug from '../util/debug'\nimport {\n effectiveIdentity,\n restoreSession,\n sessionOwnsIdentity,\n sessionWasCleared,\n subscribeIdentity,\n type IdentitySubscription,\n type SessionLike\n} from '../authSession/identityState'\nimport type { SessionWithLegacyEvents } from '../authSession/authSession'\nimport type { AuthenticationContext, AuthnLogic } from '../types'\n\n// Some auth clients (uvdsl worker-backed session) only settle restore() on\n// a worker message; a missing/unreachable RefreshWorker asset makes it hang\n// forever. This caps the wait so the login UI can never spin indefinitely.\nconst SESSION_RESTORE_TIMEOUT_MS = 5000\n\n/**\n * Await a session restore promise, but give up after\n * SESSION_RESTORE_TIMEOUT_MS and resolve with undefined so callers can\n * treat a stalled restore as \"no previous session\".\n */\nasync function withRestoreTimeout<T> (promise: Promise<T> | null): Promise<T | undefined> {\n if (promise === null) return undefined\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<undefined>(resolve => {\n timer = setTimeout(() => resolve(undefined), SESSION_RESTORE_TIMEOUT_MS)\n })\n try {\n return await Promise.race([promise, timeout])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\nexport class SolidAuthnLogic implements AuthnLogic {\n private session: SessionWithLegacyEvents\n private checkUserInFlight: Promise<NamedNode | null> | null = null\n private sessionRestoreHookAttached = false\n /**\n * This instance's subscription to the session's identity state. Its lifetime\n * IS this instance's: a cookie probe that answers after `dispose()` is\n * ignored, and the refocus listener is removed with the last instance using\n * the session (see identityState.ts).\n */\n private identity: IdentitySubscription\n\n constructor (solidAuthSession: SessionWithLegacyEvents) {\n this.session = solidAuthSession\n // The cookie-backed identity is invisible to the session (it stays inactive\n // and WebID-less), so it is re-probed when the tab regains focus: another\n // tab may have logged out or switched identity while this one was\n // backgrounded. Only meaningful where the probe applies (*.localhost NSS).\n //\n // TEMPORARY, pending the uvdsl change: this subscription exists because the\n // library does not report a cookie identity change (nor a WebID change that\n // keeps the session active). See the identityState header.\n this.identity = subscribeIdentity(solidAuthSession as unknown as SessionLike, {\n onRefocus: () => this.refreshCookieBackedFallback()\n })\n }\n\n /**\n * Detaches this instance from the session's identity state: the refocus\n * listener is removed with the last instance using the session, and a probe\n * that is still in flight can no longer apply its result.\n */\n dispose (): void {\n this.identity.unsubscribe()\n }\n\n /**\n * Re-probes the NSS cookie-backed identity. Skipped while the session owns\n * the identity — the probe only exists for the case where it does not — and\n * the state drops the result if the session takes ownership while the probe\n * is in flight.\n */\n private async refreshCookieBackedFallback (): Promise<void> {\n if (sessionOwnsIdentity(this.session as unknown as SessionLike)) return\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n }\n\n // we created authSession getter because we want to access it as authn.authSession externally\n get authSession (): SessionWithLegacyEvents { return this.session }\n\n currentUser (): NamedNode | null {\n const app = appContext()\n if (app.viewingNoAuthPage) {\n return sym(app.webId)\n }\n // The state answers with the session's identity when it owns one, the\n // probed cookie identity when the session is inactive (or cleared), and\n // nothing when neither does — a logout that retains the cached WebID must\n // not keep answering for the previous user.\n const { webId } = effectiveIdentity(this.session as unknown as SessionLike)\n return webId ? sym(webId) : offlineTestID() // null unless testing\n }\n\n /**\n * Retrieves currently logged in webId from either\n * defaultTestUser or SolidAuth\n * Also activates a session after login\n * @param [setUserCallback] Optional callback\n * @returns Resolves with webId uri, if no callback provided\n */\n async checkUser<T> (\n setUserCallback?: (me: NamedNode | null) => T\n ): Promise<NamedNode | T | null> {\n // Save hash for \"restorePreviousSession\"\n const preLoginRedirectHash = new URL(window.location.href).hash\n if (preLoginRedirectHash) {\n window.localStorage.setItem('preLoginRedirectHash', preLoginRedirectHash)\n }\n const sessionAny = this.session as any\n if (!this.sessionRestoreHookAttached && typeof sessionAny?.events?.on === 'function') {\n // Backward-compatible hook for auth clients exposing an EventEmitter-style API.\n sessionAny.events.on('sessionRestore', (url: string) => {\n debug.log(`Session restored to ${url}`)\n if (document.location.toString() !== url) history.replaceState(null, '', url)\n })\n this.sessionRestoreHookAttached = true\n }\n\n if (!this.checkUserInFlight) {\n this.checkUserInFlight = this.resolveCurrentUser()\n }\n\n const inFlight = this.checkUserInFlight\n let me: NamedNode | null\n try {\n me = await inFlight\n } finally {\n if (this.checkUserInFlight === inFlight) {\n this.checkUserInFlight = null\n }\n }\n\n return Promise.resolve(setUserCallback ? setUserCallback(me) : me)\n }\n\n private async resolveCurrentUser (): Promise<NamedNode | null> {\n const sessionAny = this.session as any\n\n /**\n * Handle a successful authentication redirect\n */\n const redirectUrl = new URL(window.location.href)\n redirectUrl.hash = ''\n if (typeof sessionAny?.handleIncomingRedirect === 'function') {\n await sessionAny.handleIncomingRedirect({\n restorePreviousSession: true,\n url: redirectUrl.href\n })\n } else {\n // uvdsl-style session (no handleIncomingRedirect): restore then handle redirect.\n //\n // The worker-backed session (WebWorkerSession) resolves restore() ONLY\n // when the SharedWorker posts a message back. If the worker asset can't\n // be fetched — local/dev servers that don't serve the RefreshWorker\n // chunk at the resolved URL, wrong MIME type, CSP, or a worker that\n // fails before `onconnect` — the promise never settles and the login\n // UI would spin forever. Race it against a timeout and treat a stall\n // as \"no previous session\" so the page can render the login button.\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n // The shared restore lock also covers this call: a refocus resync can be\n // in flight at the same time, and two overlapping restores could write an\n // older identity back over a newer one.\n const restoring = restoreSession(sessionAny)\n if (restoring) {\n try {\n await withRestoreTimeout(restoring)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n // A failed restore on an inactive session just means \"no usable\n // session to restore\" — whether that's \"No session to restore.\",\n // a stale refresh token / dead client_id returning HTTP 400, or a\n // missing session database. Never let it block the login UI: log\n // and continue as logged-out so the page renders the login button.\n // Only re-throw when the session actually became active, which is\n // an unexpected refresh failure worth surfacing.\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (isNowActive && !/no session to restore/i.test(message)) {\n throw error\n }\n debug.log(`Session restore failed, continuing logged-out: ${message}`)\n }\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('sessionRestore', window.location.href)\n }\n }\n if (typeof sessionAny?.handleRedirectFromLogin === 'function') {\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n await sessionAny.handleRedirectFromLogin()\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('login')\n }\n }\n }\n\n // Check to see if a hash was stored in local storage\n const postLoginRedirectHash = window.localStorage.getItem('preLoginRedirectHash')\n if (postLoginRedirectHash) {\n const curUrl = new URL(window.location.href)\n if (curUrl.hash !== postLoginRedirectHash) {\n if (history.pushState) {\n // debug.log('Setting window.location.has using pushState')\n history.pushState(null, document.title, postLoginRedirectHash)\n } else {\n // debug.warn('Setting window.location.has using location.hash')\n location.hash = postLoginRedirectHash\n }\n curUrl.hash = postLoginRedirectHash\n }\n // See https://stackoverflow.com/questions/3870057/how-can-i-update-window-location-hash-without-jumping-the-document\n // window.location.href = curUrl.toString()// @@ See https://developer.mozilla.org/en-US/docs/Web/API/Window/location\n window.localStorage.setItem('preLoginRedirectHash', '')\n }\n\n // Check to see if already logged in / have the WebID\n let me = offlineTestID()\n if (me) {\n return me\n }\n\n let webId = this.webIdFromSession(sessionAny?.info, sessionAny)\n if (!webId) {\n // NSS-specific fallback: recover the WebID from the NSS cookie session\n // when the client restore is empty. The result goes through the identity\n // state, which drops it if the session took ownership while the probe was\n // in flight (or if this instance was disposed meanwhile) — and reports the\n // change like any other transition.\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n webId = effectiveIdentity(sessionAny).webId ?? null\n }\n\n if (webId) {\n me = this.saveUser(webId)\n }\n\n if (me) {\n debug.log(`(Logged in as ${me} by authentication)`)\n }\n\n return me\n }\n\n private async probeNssCookieBackedWebId (): Promise<string | null> {\n if (typeof window === 'undefined') {\n return null\n }\n\n const { hostname, port, protocol } = window.location\n const localhostSuffix = '.localhost'\n // NSS local pods use subdomains like alice.localhost.\n if (!hostname.endsWith(localhostSuffix)) {\n return null\n }\n\n const podName = hostname.slice(0, -localhostSuffix.length)\n if (!podName || podName === 'localhost' || podName.includes('.')) {\n return null\n }\n\n try {\n // NSS returns 403 on this account page when the cookie session is valid.\n const probeResponse = await fetch('/account/password/change', {\n credentials: 'include',\n redirect: 'manual',\n cache: 'no-store'\n })\n if (probeResponse.status !== 403) {\n return null\n }\n const origin = `${protocol}//${hostname}${port ? `:${port}` : ''}`\n return `${origin}/profile/card#me`\n } catch (_error) {\n return null\n }\n }\n\n /**\n * Saves `webId` in `context.me`\n * @param webId\n * @param context\n *\n * @returns Returns the WebID, after setting it\n */\n saveUser (\n webId: NamedNode | string | null,\n context?: AuthenticationContext\n ): NamedNode | null {\n let webIdUri: string\n if (webId) {\n webIdUri = (typeof webId === 'string') ? webId : webId.uri\n const me = namedNode(webIdUri)\n if (context) {\n context.me = me\n }\n return me\n }\n return null\n }\n\n /**\n * @returns {Promise<string|null>} Resolves with WebID URI or null\n */\n webIdFromSession (\n sessionInfo?: { webId?: string, isLoggedIn?: boolean },\n sessionRoot?: { webId?: string, isLoggedIn?: boolean, isActive?: boolean }\n ): string | null {\n const webId = sessionInfo?.webId || sessionRoot?.webId\n if (!webId) {\n return null\n }\n const infoLoggedIn = sessionInfo?.isLoggedIn\n const rootLoggedIn = sessionRoot?.isLoggedIn\n const rootActive = sessionRoot?.isActive\n // An explicit inactive/not-logged-in flag wins over a cached WebID and\n // over a positive flag in another source — the same rule the identity\n // state uses (see identityState.ts). The session root has no `isLoggedIn`\n // property, so requiring every source to be false kept a cached WebID\n // alive across a logout; a mixed snapshot must not resurrect one either. A\n // session whose backing store lost it is inactive as well, however\n // positive its own fields still look.\n if (sessionWasCleared(sessionRoot) ||\n infoLoggedIn === false || rootLoggedIn === false || rootActive === false) {\n return null\n }\n // Active, or a legacy session that reports no state at all.\n return webId\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SolidAuthnLogic.esm.js","names":[],"sources":["../../src/authn/SolidAuthnLogic.ts"],"sourcesContent":["import { namedNode, NamedNode, sym } from 'rdflib'\nimport { appContext, offlineTestID } from './authUtil'\nimport * as debug from '../util/debug'\nimport {\n effectiveIdentity,\n restoreSession,\n sessionOwnsIdentity,\n sessionWasCleared,\n subscribeIdentity,\n type IdentitySubscription,\n type SessionLike\n} from '../authSession/identityState'\nimport type { SessionWithLegacyEvents } from '../authSession/authSession'\nimport type { AuthenticationContext, AuthnLogic } from '../types'\n\n// Some auth clients (uvdsl worker-backed session) only settle restore() on\n// a worker message; a missing/unreachable RefreshWorker asset makes it hang\n// forever. This caps the wait so the login UI can never spin indefinitely.\nconst SESSION_RESTORE_TIMEOUT_MS = 5000\n\n/**\n * Await a session restore promise, but give up after\n * SESSION_RESTORE_TIMEOUT_MS and resolve with undefined so callers can\n * treat a stalled restore as \"no previous session\".\n */\nasync function withRestoreTimeout<T> (promise: Promise<T> | null): Promise<T | undefined> {\n if (promise === null) return undefined\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<undefined>(resolve => {\n timer = setTimeout(() => resolve(undefined), SESSION_RESTORE_TIMEOUT_MS)\n })\n try {\n return await Promise.race([promise, timeout])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\nexport class SolidAuthnLogic implements AuthnLogic {\n private session: SessionWithLegacyEvents\n private checkUserInFlight: Promise<NamedNode | null> | null = null\n private sessionRestoreHookAttached = false\n /**\n * This instance's subscription to the session's identity state. Its lifetime\n * IS this instance's: a cookie probe that answers after `dispose()` is\n * ignored, and the refocus listener is removed with the last instance using\n * the session (see identityState.ts).\n */\n private identity: IdentitySubscription\n\n constructor (solidAuthSession: SessionWithLegacyEvents) {\n this.session = solidAuthSession\n // The cookie-backed identity is invisible to the session (it stays inactive\n // and WebID-less), so it is re-probed when the tab regains focus: another\n // tab may have logged out or switched identity while this one was\n // backgrounded. Only meaningful where the probe applies (*.localhost NSS).\n //\n // TEMPORARY, pending the uvdsl change: this subscription exists because the\n // library does not report a cookie identity change (nor a WebID change that\n // keeps the session active). See the identityState header.\n this.identity = subscribeIdentity(solidAuthSession as unknown as SessionLike, {\n onRefocus: () => this.refreshCookieBackedFallback()\n })\n }\n\n /**\n * Detaches this instance from the session's identity state: the refocus\n * listener is removed with the last instance using the session, and a probe\n * that is still in flight can no longer apply its result.\n */\n dispose (): void {\n this.identity.unsubscribe()\n }\n\n /**\n * Re-probes the NSS cookie-backed identity. Skipped while the session owns\n * the identity — the probe only exists for the case where it does not — and\n * the state drops the result if the session takes ownership while the probe\n * is in flight.\n */\n private async refreshCookieBackedFallback (): Promise<void> {\n if (sessionOwnsIdentity(this.session as unknown as SessionLike)) return\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n }\n\n // we created authSession getter because we want to access it as authn.authSession externally\n get authSession (): SessionWithLegacyEvents { return this.session }\n\n currentUser (): NamedNode | null {\n const app = appContext()\n if (app.viewingNoAuthPage) {\n return sym(app.webId)\n }\n // The state answers with the session's identity when it owns one, the\n // probed cookie identity when the session is inactive (or cleared), and\n // nothing when neither does — a logout that retains the cached WebID must\n // not keep answering for the previous user.\n const { webId } = effectiveIdentity(this.session as unknown as SessionLike)\n return webId ? sym(webId) : offlineTestID() // null unless testing\n }\n\n /**\n * Retrieves currently logged in webId from either\n * defaultTestUser or SolidAuth\n * Also activates a session after login\n * @param [setUserCallback] Optional callback\n * @returns Resolves with webId uri, if no callback provided\n */\n async checkUser<T> (\n setUserCallback?: (me: NamedNode | null) => T\n ): Promise<NamedNode | T | null> {\n // Save hash for \"restorePreviousSession\"\n const preLoginRedirectHash = new URL(window.location.href).hash\n if (preLoginRedirectHash) {\n window.localStorage.setItem('preLoginRedirectHash', preLoginRedirectHash)\n }\n const sessionAny = this.session as any\n if (!this.sessionRestoreHookAttached && typeof sessionAny?.events?.on === 'function') {\n // Backward-compatible hook for auth clients exposing an EventEmitter-style API.\n sessionAny.events.on('sessionRestore', (url: string) => {\n debug.log(`Session restored to ${url}`)\n if (document.location.toString() !== url) history.replaceState(null, '', url)\n })\n this.sessionRestoreHookAttached = true\n }\n\n if (!this.checkUserInFlight) {\n this.checkUserInFlight = this.resolveCurrentUser()\n }\n\n const inFlight = this.checkUserInFlight\n let me: NamedNode | null\n try {\n me = await inFlight\n } finally {\n if (this.checkUserInFlight === inFlight) {\n this.checkUserInFlight = null\n }\n }\n\n return Promise.resolve(setUserCallback ? setUserCallback(me) : me)\n }\n\n private async resolveCurrentUser (): Promise<NamedNode | null> {\n const sessionAny = this.session as any\n\n /**\n * Handle a successful authentication redirect\n */\n const redirectUrl = new URL(window.location.href)\n redirectUrl.hash = ''\n if (typeof sessionAny?.handleIncomingRedirect === 'function') {\n await sessionAny.handleIncomingRedirect({\n restorePreviousSession: true,\n url: redirectUrl.href\n })\n } else {\n // uvdsl-style session (no handleIncomingRedirect): restore then handle redirect.\n //\n // The worker-backed session (WebWorkerSession) resolves restore() ONLY\n // when the SharedWorker posts a message back. If the worker asset can't\n // be fetched — local/dev servers that don't serve the RefreshWorker\n // chunk at the resolved URL, wrong MIME type, CSP, or a worker that\n // fails before `onconnect` — the promise never settles and the login\n // UI would spin forever. Race it against a timeout and treat a stall\n // as \"no previous session\" so the page can render the login button.\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n // The shared restore lock also covers this call: a refocus resync can be\n // in flight at the same time, and two overlapping restores could write an\n // older identity back over a newer one.\n const restoring = restoreSession(sessionAny)\n if (restoring) {\n try {\n await withRestoreTimeout(restoring)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n // A failed restore on an inactive session just means \"no usable\n // session to restore\" — whether that's \"No session to restore.\",\n // a stale refresh token / dead client_id returning HTTP 400, or a\n // missing session database. Never let it block the login UI: log\n // and continue as logged-out so the page renders the login button.\n // Only re-throw when the session actually became active, which is\n // an unexpected refresh failure worth surfacing.\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (isNowActive && !/no session to restore/i.test(message)) {\n throw error\n }\n debug.log(`Session restore failed, continuing logged-out: ${message}`)\n }\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('sessionRestore', window.location.href)\n }\n }\n if (typeof sessionAny?.handleRedirectFromLogin === 'function') {\n const wasActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n await sessionAny.handleRedirectFromLogin()\n const isNowActive = sessionAny?.isActive ?? Boolean(sessionAny?.webId)\n if (!wasActive && isNowActive) {\n sessionAny.events?.emit('login')\n }\n }\n }\n\n // Check to see if a hash was stored in local storage\n const postLoginRedirectHash = window.localStorage.getItem('preLoginRedirectHash')\n if (postLoginRedirectHash) {\n const curUrl = new URL(window.location.href)\n if (curUrl.hash !== postLoginRedirectHash) {\n if (history.pushState) {\n // debug.log('Setting window.location.has using pushState')\n history.pushState(null, document.title, postLoginRedirectHash)\n } else {\n // debug.warn('Setting window.location.has using location.hash')\n location.hash = postLoginRedirectHash\n }\n curUrl.hash = postLoginRedirectHash\n }\n // See https://stackoverflow.com/questions/3870057/how-can-i-update-window-location-hash-without-jumping-the-document\n // window.location.href = curUrl.toString()// @@ See https://developer.mozilla.org/en-US/docs/Web/API/Window/location\n window.localStorage.setItem('preLoginRedirectHash', '')\n }\n\n // Check to see if already logged in / have the WebID\n let me = offlineTestID()\n if (me) {\n return me\n }\n\n let webId = this.webIdFromSession(sessionAny?.info, sessionAny)\n if (!webId) {\n // NSS-specific fallback: recover the WebID from the NSS cookie session\n // when the client restore is empty. The result goes through the identity\n // state, which drops it if the session took ownership while the probe was\n // in flight (or if this instance was disposed meanwhile) — and reports the\n // change like any other transition.\n this.identity.reportCookieIdentity(await this.probeNssCookieBackedWebId())\n webId = effectiveIdentity(sessionAny).webId ?? null\n }\n\n if (webId) {\n me = this.saveUser(webId)\n }\n\n if (me) {\n debug.log(`(Logged in as ${me} by authentication)`)\n }\n\n return me\n }\n\n private async probeNssCookieBackedWebId (): Promise<string | null> {\n if (typeof window === 'undefined') {\n return null\n }\n\n const { hostname, port, protocol } = window.location\n const localhostSuffix = '.localhost'\n // NSS local pods use subdomains like alice.localhost.\n if (!hostname.endsWith(localhostSuffix)) {\n return null\n }\n\n const podName = hostname.slice(0, -localhostSuffix.length)\n if (!podName || podName === 'localhost' || podName.includes('.')) {\n return null\n }\n\n try {\n // NSS returns 403 on this account page when the cookie session is valid.\n const probeResponse = await fetch('/account/password/change', {\n credentials: 'include',\n redirect: 'manual',\n cache: 'no-store'\n })\n if (probeResponse.status !== 403) {\n return null\n }\n const origin = `${protocol}//${hostname}${port ? `:${port}` : ''}`\n return `${origin}/profile/card#me`\n } catch (_error) {\n return null\n }\n }\n\n /**\n * Saves `webId` in `context.me`\n * @param webId\n * @param context\n *\n * @returns Returns the WebID, after setting it\n */\n saveUser (\n webId: NamedNode | string | null,\n context?: AuthenticationContext\n ): NamedNode | null {\n let webIdUri: string\n if (webId) {\n webIdUri = (typeof webId === 'string') ? webId : webId.uri\n const me = namedNode(webIdUri)\n if (context) {\n context.me = me\n }\n return me\n }\n return null\n }\n\n /**\n * @returns {Promise<string|null>} Resolves with WebID URI or null\n */\n webIdFromSession (\n sessionInfo?: { webId?: string, isLoggedIn?: boolean },\n sessionRoot?: { webId?: string, isLoggedIn?: boolean, isActive?: boolean }\n ): string | null {\n const webId = sessionInfo?.webId || sessionRoot?.webId\n if (!webId) {\n return null\n }\n const infoLoggedIn = sessionInfo?.isLoggedIn\n const rootLoggedIn = sessionRoot?.isLoggedIn\n const rootActive = sessionRoot?.isActive\n // An explicit inactive/not-logged-in flag wins over a cached WebID and\n // over a positive flag in another source — the same rule the identity\n // state uses (see identityState.ts). The session root has no `isLoggedIn`\n // property, so requiring every source to be false kept a cached WebID\n // alive across a logout; a mixed snapshot must not resurrect one either. A\n // session whose backing store lost it is inactive as well, however\n // positive its own fields still look.\n if (sessionWasCleared(sessionRoot) ||\n infoLoggedIn === false || rootLoggedIn === false || rootActive === false) {\n return null\n }\n // Active, or a legacy session that reports no state at all.\n return webId\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"authUtil.cjs.js","names":[],"sources":["../../src/authn/authUtil.ts"],"sourcesContent":["import { NamedNode, sym } from 'rdflib'\nimport * as debug from '../util/debug'\n\n/**\n * find a user or app's context as set in window.SolidAppContext\n * this is a const, not a function, because we have problems to jest mock it otherwise\n * see: https://github.com/facebook/jest/issues/936#issuecomment-545080082 for more\n * @return {any} - an appContext object\n */\nexport const appContext = ():any => {\n let { SolidAppContext }: any = window\n SolidAppContext ||= {}\n SolidAppContext.viewingNoAuthPage = false\n if (SolidAppContext.noAuth && window.document) {\n const currentPage = window.document.location.href\n if (currentPage.startsWith(SolidAppContext.noAuth)) {\n SolidAppContext.viewingNoAuthPage = true\n const params = new URLSearchParams(window.document.location.search)\n if (params) {\n let viewedPage = SolidAppContext.viewedPage = params.get('uri') || null\n if (viewedPage) {\n viewedPage = decodeURI(viewedPage)\n if (!viewedPage.startsWith(SolidAppContext.noAuth)) {\n const ary = viewedPage.split(/\\//)\n SolidAppContext.idp = ary[0] + '//' + ary[2]\n SolidAppContext.viewingNoAuthPage = false\n }\n }\n }\n }\n }\n return SolidAppContext\n}\n\n/**\n * Returns `sym($SolidTestEnvironment.username)` if\n * `$SolidTestEnvironment.username` is defined as a global\n * or \n * returns testID defined in the HTML page\n * @returns {NamedNode|null}\n */\nexport function offlineTestID (): NamedNode | null {\n const { $SolidTestEnvironment }: any = window\n if (\n typeof $SolidTestEnvironment !== 'undefined' &&\n $SolidTestEnvironment.username\n ) {\n // Test setup\n debug.log('Assuming the user is ' + $SolidTestEnvironment.username)\n return sym($SolidTestEnvironment.username)\n }\n // hack that makes SolidOS work in offline mode by adding the webId directly in html\n // example usage: https://github.com/solidos/mashlib/blob/29b8b53c46bf02e0e219f0bacd51b0e9951001dd/test/contact/local.html#L37\n if (\n typeof document !== 'undefined' &&\n document.location &&\n ('' + document.location).slice(0, 16) === 'http://localhost'\n ) {\n const div = document.getElementById('appTarget')\n if (!div) return null\n const id = div.getAttribute('testID')\n if (!id) return null\n debug.log('Assuming user is ' + id)\n return sym(id)\n }\n return null\n}\n"],"mappings":"gEASA,IAAa,MAAuB,CAChC,GAAI,CAAE,mBAAyB,OAG/B,GAFA,IAAoB,CAAC,EACrB,EAAgB,kBAAoB,GAChC,EAAgB,QAAU,OAAO,UACb,OAAO,SAAS,SAAS,KAC7B,WAAW,EAAgB,MAAM,EAAG,CACpD,EAAgB,kBAAoB,GACpC,IAAM,EAAS,IAAI,gBAAgB,OAAO,SAAS,SAAS,MAAM,EAClE,GAAI,EAAQ,CACR,IAAI,EAAa,EAAgB,WAAa,EAAO,IAAI,KAAK,GAAK,KACnE,GAAI,IACJ,EAAa,UAAU,CAAU,EAC7B,CAAC,EAAW,WAAW,EAAgB,MAAM,GAAG,CAChD,IAAM,EAAM,EAAW,MAAM,IAAI,EACjC,EAAgB,IAAM,EAAI,GAAK,KAAO,EAAI,GAC1C,EAAgB,kBAAoB,EACxC,CAEJ,CACA,CAEJ,OAAO,CACX,EASA,SAAgB,GAAmC,CAC/C,GAAM,CAAE,yBAA+B,OACvC,GACS,IAA0B,QACjC,EAAsB,SAItB,OADA,EAAA,IAAU,wBAA0B,EAAsB,QAAQ,GAClE,EAAA,EAAA,IAAA,CAAW,EAAsB,QAAQ,EAI3C,GACE,OAAO,SAAa,KACpB,SAAS,WACR,GAAK,SAAS,SAAA,CAAU,MAAM,EAAG,EAAE,IAAM,mBAC1C,CACA,IAAM,EAAM,SAAS,eAAe,WAAW,EAC/C,GAAI,CAAC,EAAK,OAAO,KACjB,IAAM,EAAK,EAAI,aAAa,QAAQ,EAGpC,OAFK,GACL,EAAA,IAAU,oBAAsB,CAAE,GAClC,EAAA,EAAA,IAAA,CAAW,CAAE,GAFG,IAGlB,CACA,OAAO,IACX"}
1
+ {"version":3,"file":"authUtil.cjs.js","names":[],"sources":["../../src/authn/authUtil.ts"],"sourcesContent":["import { NamedNode, sym } from 'rdflib'\nimport * as debug from '../util/debug'\n\n/**\n * find a user or app's context as set in window.SolidAppContext\n * this is a const, not a function, because we have problems to jest mock it otherwise\n * see: https://github.com/facebook/jest/issues/936#issuecomment-545080082 for more\n * @return {any} - an appContext object\n */\nexport const appContext = ():any => {\n let { SolidAppContext }: any = window\n SolidAppContext ||= {}\n SolidAppContext.viewingNoAuthPage = false\n if (SolidAppContext.noAuth && window.document) {\n const currentPage = window.document.location.href\n if (currentPage.startsWith(SolidAppContext.noAuth)) {\n SolidAppContext.viewingNoAuthPage = true\n const params = new URLSearchParams(window.document.location.search)\n if (params) {\n let viewedPage = SolidAppContext.viewedPage = params.get('uri') || null\n if (viewedPage) {\n viewedPage = decodeURI(viewedPage)\n if (!viewedPage.startsWith(SolidAppContext.noAuth)) {\n const ary = viewedPage.split(/\\//)\n SolidAppContext.idp = ary[0] + '//' + ary[2]\n SolidAppContext.viewingNoAuthPage = false\n }\n }\n }\n }\n }\n return SolidAppContext\n}\n\n/**\n * Returns `sym($SolidTestEnvironment.username)` if\n * `$SolidTestEnvironment.username` is defined as a global\n * or \n * returns testID defined in the HTML page\n * @returns {NamedNode|null}\n */\nexport function offlineTestID (): NamedNode | null {\n const { $SolidTestEnvironment }: any = window\n if (\n typeof $SolidTestEnvironment !== 'undefined' &&\n $SolidTestEnvironment.username\n ) {\n // Test setup\n debug.log('Assuming the user is ' + $SolidTestEnvironment.username)\n return sym($SolidTestEnvironment.username)\n }\n // hack that makes SolidOS work in offline mode by adding the webId directly in html\n // example usage: https://github.com/solidos/mashlib/blob/29b8b53c46bf02e0e219f0bacd51b0e9951001dd/test/contact/local.html#L37\n if (\n typeof document !== 'undefined' &&\n document.location &&\n ('' + document.location).slice(0, 16) === 'http://localhost'\n ) {\n const div = document.getElementById('appTarget')\n if (!div) return null\n const id = div.getAttribute('testID')\n if (!id) return null\n debug.log('Assuming user is ' + id)\n return sym(id)\n }\n return null\n}\n"],"mappings":"gEASA,IAAa,MAAuB,CAChC,GAAI,CAAE,mBAAyB,OAG/B,GAFA,IAAoB,CAAC,EACrB,EAAgB,kBAAoB,GAChC,EAAgB,QAAU,OAAO,UACb,OAAO,SAAS,SAAS,KAC7B,WAAW,EAAgB,MAAM,EAAG,CACpD,EAAgB,kBAAoB,GACpC,IAAM,EAAS,IAAI,gBAAgB,OAAO,SAAS,SAAS,MAAM,EAClE,GAAI,EAAQ,CACR,IAAI,EAAa,EAAgB,WAAa,EAAO,IAAI,KAAK,GAAK,KACnE,GAAI,IACJ,EAAa,UAAU,CAAU,EAC7B,CAAC,EAAW,WAAW,EAAgB,MAAM,GAAG,CAChD,IAAM,EAAM,EAAW,MAAM,IAAI,EACjC,EAAgB,IAAM,EAAI,GAAK,KAAO,EAAI,GAC1C,EAAgB,kBAAoB,EACxC,CAEJ,CACA,CAEJ,OAAO,CACX,EASA,SAAgB,GAAmC,CAC/C,GAAM,CAAE,yBAA+B,OACvC,GACS,IAA0B,QACjC,EAAsB,SAItB,OADA,EAAA,IAAU,wBAA0B,EAAsB,QAAQ,GAClE,EAAO,EAAA,IAAA,CAAI,EAAsB,QAAQ,EAI3C,GACE,OAAO,SAAa,KACpB,SAAS,WACR,GAAK,SAAS,SAAA,CAAU,MAAM,EAAG,EAAE,IAAM,mBAC1C,CACA,IAAM,EAAM,SAAS,eAAe,WAAW,EAC/C,GAAI,CAAC,EAAK,OAAO,KACjB,IAAM,EAAK,EAAI,aAAa,QAAQ,EAGpC,OAFK,GACL,EAAA,IAAU,oBAAsB,CAAE,GAClC,EAAO,EAAA,IAAA,CAAI,CAAE,GAFG,IAGlB,CACA,OAAO,IACX"}
@@ -0,0 +1,17 @@
1
+ import { NamedNode } from 'rdflib';
2
+ /**
3
+ * find a user or app's context as set in window.SolidAppContext
4
+ * this is a const, not a function, because we have problems to jest mock it otherwise
5
+ * see: https://github.com/facebook/jest/issues/936#issuecomment-545080082 for more
6
+ * @return {any} - an appContext object
7
+ */
8
+ export declare const appContext: () => any;
9
+ /**
10
+ * Returns `sym($SolidTestEnvironment.username)` if
11
+ * `$SolidTestEnvironment.username` is defined as a global
12
+ * or
13
+ * returns testID defined in the HTML page
14
+ * @returns {NamedNode|null}
15
+ */
16
+ export declare function offlineTestID(): NamedNode | null;
17
+ //# sourceMappingURL=authUtil.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authUtil.d.ts","sourceRoot":"","sources":["../../src/authn/authUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAO,MAAM,QAAQ,CAAA;AAGvC;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAAM,GAuB5B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAK,SAAS,GAAG,IAAI,CAyBjD"}
@@ -0,0 +1,6 @@
1
+ export type ServerLogoutOptions = {
2
+ issuer?: string;
3
+ postLogoutRedirectPath?: string;
4
+ };
5
+ export declare function performServerSideLogout(options?: ServerLogoutOptions): Promise<boolean>;
6
+ //# sourceMappingURL=serverLogout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverLogout.d.ts","sourceRoot":"","sources":["../../src/authn/serverLogout.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC,CAAA;AAED,wBAAsB,uBAAuB,CAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC,CAqClG"}
@@ -14,7 +14,7 @@ const e=require("../util/ns.cjs.js"),t=require("../util/utils.cjs.js");let n=req
14
14
  acl:default <.>;
15
15
  acl:mode
16
16
  acl:Read, acl:Append.
17
- `;await i.fetcher.webOperation(`PUT`,a.value,{data:o,contentType:`text/turtle`})}async function c(e,r){let a=i.any(r,o.solid(`privateTypeIndex`));if(!a)throw Error(`Private type index not found!`);await i.fetcher.load(a);let s=t.newThing(a),c=[(0,n.st)(s,o.rdf(`type`),o.solid(`TypeRegistration`),a.doc()),(0,n.st)(s,o.solid(`forClass`),o.meeting(`LongChat`),a.doc()),(0,n.st)(s,o.solid(`instance`),e,a.doc())];await new Promise((e,t)=>{i.updater.update([],c,function(n,r,i){r?e(null):t(Error(i))})})}async function l(e){let r=await a.loadMe(),o=t.determineChatContainer(e,await a.getPodRoot(r)),s=!0;try{await i.fetcher.load(new n.NamedNode(o.value+`index.ttl#this`))}catch{s=!1}return{me:r,chatContainer:o,exists:s}}async function u(e,t){return(await d({me:t,newBase:e.value})).newInstance}function d(e){let t=i,a=t.updater;if(e.me&&!e.me.uri)throw Error(`chat mintNew: Invalid userid `+e.me);let s=e.newInstance=e.newInstance||t.sym(e.newBase+r),c=s.doc();return t.add(s,o.rdf(`type`),o.meeting(`LongChat`),c),t.add(s,o.dc(`title`),`Chat channel`,c),t.add(s,o.dc(`created`),(0,n.term)(new Date(Date.now())),c),e.me&&t.add(s,o.dc(`author`),e.me,c),new Promise(function(n,r){a?.put(c,t.statementsMatching(void 0,void 0,void 0,c),`text/turtle`,function(t,i,a){i?n({...e,newInstance:s}):r(Error(`FAILED to save new chat channel at: `+t+` : `+a))})})}async function f(e,t=!0){let{me:i,chatContainer:a,exists:o}=await l(e);if(o)return new n.NamedNode(a.value+r);if(t){let t=await u(a,i);return await p(e,t),await s(a,i,e),await c(t,i),t}return null}async function p(e,t){await i.fetcher.load(e.doc());let n=i.any(e,o.ldp(`inbox`),void 0,e.doc());if(!n)throw Error(`Invitee inbox not found! ${e.value}`);let r=`
17
+ `;await i.fetcher.webOperation(`PUT`,a.value,{data:o,contentType:`text/turtle`})}async function c(e,r){let a=i.any(r,o.solid(`privateTypeIndex`));if(!a)throw Error(`Private type index not found!`);await i.fetcher.load(a);let s=t.newThing(a),c=[(0,n.st)(s,o.rdf(`type`),o.solid(`TypeRegistration`),a.doc()),(0,n.st)(s,o.solid(`forClass`),o.meeting(`LongChat`),a.doc()),(0,n.st)(s,o.solid(`instance`),e,a.doc())];await new Promise((e,t)=>{i.updater.update([],c,function(n,r,i){r?e(null):t(Error(i))})})}async function l(e){let r=await a.loadMe(),o=await a.getPodRoot(r),s=t.determineChatContainer(e,o),c=!0;try{await i.fetcher.load(new n.NamedNode(s.value+`index.ttl#this`))}catch{c=!1}return{me:r,chatContainer:s,exists:c}}async function u(e,t){return(await d({me:t,newBase:e.value})).newInstance}function d(e){let t=i,a=t.updater;if(e.me&&!e.me.uri)throw Error(`chat mintNew: Invalid userid `+e.me);let s=e.newInstance=e.newInstance||t.sym(e.newBase+r),c=s.doc();return t.add(s,o.rdf(`type`),o.meeting(`LongChat`),c),t.add(s,o.dc(`title`),`Chat channel`,c),t.add(s,o.dc(`created`),(0,n.term)(new Date(Date.now())),c),e.me&&t.add(s,o.dc(`author`),e.me,c),new Promise(function(n,r){a?.put(c,t.statementsMatching(void 0,void 0,void 0,c),`text/turtle`,function(t,i,a){i?n({...e,newInstance:s}):r(Error(`FAILED to save new chat channel at: `+t+` : `+a))})})}async function f(e,t=!0){let{me:i,chatContainer:a,exists:o}=await l(e);if(o)return new n.NamedNode(a.value+r);if(t){let t=await u(a,i);return await p(e,t),await s(a,i,e),await c(t,i),t}return null}async function p(e,t){await i.fetcher.load(e.doc());let n=i.any(e,o.ldp(`inbox`),void 0,e.doc());if(!n)throw Error(`Invitee inbox not found! ${e.value}`);let r=`
18
18
  <> a <http://www.w3.org/ns/pim/meeting#LongChatInvite> ;
19
19
  ${o.rdf(`seeAlso`)} <${t.value}> .
20
20
  `,a=await i.fetcher?.webOperation(`POST`,n.value,{data:r,contentType:`text/turtle`});if(!a?.headers.get(`location`))throw Error(`Invite sending returned a ${a?.status}`)}return{setAcl:s,addToPrivateTypeIndex:c,findChat:l,createChatThing:u,getChat:f,sendInvite:p,mintNew:d}}exports.createChatLogic=i;
@@ -1 +1 @@
1
- {"version":3,"file":"chatLogic.cjs.js","names":[],"sources":["../../src/chat/chatLogic.ts"],"sourcesContent":["import { NamedNode, Node, st, term } from 'rdflib'\nimport { ChatLogic, CreatedPaneOptions, NewPaneOptions, Chat } from '../types'\nimport { ns as namespace } from '../util/ns'\nimport { determineChatContainer, newThing } from '../util/utils'\n\nconst CHAT_LOCATION_IN_CONTAINER = 'index.ttl#this'\n\nexport function createChatLogic(store, profileLogic): ChatLogic {\n const ns = namespace\n\n async function setAcl(\n chatContainer: NamedNode,\n me: NamedNode,\n invitee: NamedNode\n ): Promise<void> {\n // Some servers don't present a Link http response header\n // if the container doesn't exist yet, so refetch the container\n // now that it has been created:\n await store.fetcher.load(chatContainer)\n\n // FIXME: check the Why value on this quad:\n const chatAclDoc = store.any(\n chatContainer,\n new NamedNode('http://www.iana.org/assignments/link-relations/acl')\n )\n if (!chatAclDoc) {\n throw new Error('Chat ACL doc not found!')\n }\n\n const aclBody = `\n @prefix acl: <http://www.w3.org/ns/auth/acl#>.\n <#owner>\n a acl:Authorization;\n acl:agent <${me.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Write, acl:Control.\n <#invitee>\n a acl:Authorization;\n acl:agent <${invitee.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Append.\n `\n await store.fetcher.webOperation('PUT', chatAclDoc.value, {\n data: aclBody,\n contentType: 'text/turtle',\n })\n }\n\n async function addToPrivateTypeIndex(chatThing, me) {\n // Add to private type index\n const privateTypeIndex = store.any(\n me,\n ns.solid('privateTypeIndex')\n ) as NamedNode | null\n if (!privateTypeIndex) {\n throw new Error('Private type index not found!')\n }\n await store.fetcher.load(privateTypeIndex)\n const reg = newThing(privateTypeIndex)\n const ins = [\n st(\n reg,\n ns.rdf('type'),\n ns.solid('TypeRegistration'),\n privateTypeIndex.doc()\n ),\n st(\n reg,\n ns.solid('forClass'),\n ns.meeting('LongChat'),\n privateTypeIndex.doc()\n ),\n st(reg, ns.solid('instance'), chatThing, privateTypeIndex.doc()),\n ]\n await new Promise((resolve, reject) => {\n store.updater.update([], ins, function (_uri, ok, errm) {\n if (!ok) {\n reject(new Error(errm))\n } else {\n resolve(null)\n }\n })\n })\n }\n\n async function findChat(invitee: NamedNode): Promise<Chat> {\n const me = await profileLogic.loadMe()\n const podRoot = await profileLogic.getPodRoot(me)\n const chatContainer = determineChatContainer(invitee, podRoot)\n let exists = true\n try {\n await store.fetcher.load(\n new NamedNode(chatContainer.value + 'index.ttl#this')\n )\n } catch (e) {\n exists = false\n }\n return { me, chatContainer, exists }\n }\n\n async function createChatThing(\n chatContainer: NamedNode,\n me: NamedNode\n ): Promise<NamedNode> {\n const created = await mintNew({\n me,\n newBase: chatContainer.value,\n })\n return created.newInstance\n }\n\n function mintNew(newPaneOptions: NewPaneOptions): Promise<CreatedPaneOptions> {\n const kb = store\n const updater = kb.updater\n if (newPaneOptions.me && !newPaneOptions.me.uri) {\n throw new Error('chat mintNew: Invalid userid ' + newPaneOptions.me)\n }\n\n const newInstance = (newPaneOptions.newInstance =\n newPaneOptions.newInstance ||\n kb.sym(newPaneOptions.newBase + CHAT_LOCATION_IN_CONTAINER))\n const newChatDoc = newInstance.doc()\n\n kb.add(\n newInstance,\n ns.rdf('type'),\n ns.meeting('LongChat'),\n newChatDoc\n )\n kb.add(newInstance, ns.dc('title'), 'Chat channel', newChatDoc)\n kb.add(\n newInstance,\n ns.dc('created'),\n term<Node>(new Date(Date.now())),\n newChatDoc\n )\n if (newPaneOptions.me) {\n kb.add(newInstance, ns.dc('author'), newPaneOptions.me, newChatDoc)\n }\n\n return new Promise(function (resolve, reject) {\n updater?.put(\n newChatDoc,\n kb.statementsMatching(undefined, undefined, undefined, newChatDoc),\n 'text/turtle',\n function (uri2, ok, message) {\n if (ok) {\n resolve({\n ...newPaneOptions,\n newInstance,\n })\n } else {\n reject(\n new Error(\n 'FAILED to save new chat channel at: ' + uri2 + ' : ' + message\n )\n )\n }\n }\n )\n })\n }\n\n /**\n * Find (and optionally create) an individual chat between the current user and the given invitee\n * @param invitee - The person to chat with\n * @param createIfMissing - Whether the chat should be created, if missing\n * @returns null if missing, or a node referring to an already existing chat, or the newly created chat\n */\n async function getChat(\n invitee: NamedNode,\n createIfMissing = true\n ): Promise<NamedNode | null> {\n const { me, chatContainer, exists } = await findChat(invitee)\n if (exists) {\n return new NamedNode(chatContainer.value + CHAT_LOCATION_IN_CONTAINER)\n }\n\n if (createIfMissing) {\n const chatThing = await createChatThing(chatContainer, me)\n await sendInvite(invitee, chatThing)\n await setAcl(chatContainer, me, invitee)\n await addToPrivateTypeIndex(chatThing, me)\n return chatThing\n }\n return null\n }\n\n async function sendInvite(invitee: NamedNode, chatThing: NamedNode) {\n await store.fetcher.load(invitee.doc())\n const inviteeInbox = store.any(\n invitee,\n ns.ldp('inbox'),\n undefined,\n invitee.doc()\n )\n if (!inviteeInbox) {\n throw new Error(`Invitee inbox not found! ${invitee.value}`)\n }\n const inviteBody = `\n <> a <http://www.w3.org/ns/pim/meeting#LongChatInvite> ;\n ${ns.rdf('seeAlso')} <${chatThing.value}> .\n `\n\n const inviteResponse = await store.fetcher?.webOperation(\n 'POST',\n inviteeInbox.value,\n {\n data: inviteBody,\n contentType: 'text/turtle',\n }\n )\n const locationStr = inviteResponse?.headers.get('location')\n if (!locationStr) {\n throw new Error(`Invite sending returned a ${inviteResponse?.status}`)\n }\n }\n return {\n setAcl, addToPrivateTypeIndex, findChat, createChatThing, getChat, sendInvite, mintNew\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"chatLogic.cjs.js","names":[],"sources":["../../src/chat/chatLogic.ts"],"sourcesContent":["import { NamedNode, Node, st, term } from 'rdflib'\nimport { ChatLogic, CreatedPaneOptions, NewPaneOptions, Chat } from '../types'\nimport { ns as namespace } from '../util/ns'\nimport { determineChatContainer, newThing } from '../util/utils'\n\nconst CHAT_LOCATION_IN_CONTAINER = 'index.ttl#this'\n\nexport function createChatLogic(store, profileLogic): ChatLogic {\n const ns = namespace\n\n async function setAcl(\n chatContainer: NamedNode,\n me: NamedNode,\n invitee: NamedNode\n ): Promise<void> {\n // Some servers don't present a Link http response header\n // if the container doesn't exist yet, so refetch the container\n // now that it has been created:\n await store.fetcher.load(chatContainer)\n\n // FIXME: check the Why value on this quad:\n const chatAclDoc = store.any(\n chatContainer,\n new NamedNode('http://www.iana.org/assignments/link-relations/acl')\n )\n if (!chatAclDoc) {\n throw new Error('Chat ACL doc not found!')\n }\n\n const aclBody = `\n @prefix acl: <http://www.w3.org/ns/auth/acl#>.\n <#owner>\n a acl:Authorization;\n acl:agent <${me.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Write, acl:Control.\n <#invitee>\n a acl:Authorization;\n acl:agent <${invitee.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Append.\n `\n await store.fetcher.webOperation('PUT', chatAclDoc.value, {\n data: aclBody,\n contentType: 'text/turtle',\n })\n }\n\n async function addToPrivateTypeIndex(chatThing, me) {\n // Add to private type index\n const privateTypeIndex = store.any(\n me,\n ns.solid('privateTypeIndex')\n ) as NamedNode | null\n if (!privateTypeIndex) {\n throw new Error('Private type index not found!')\n }\n await store.fetcher.load(privateTypeIndex)\n const reg = newThing(privateTypeIndex)\n const ins = [\n st(\n reg,\n ns.rdf('type'),\n ns.solid('TypeRegistration'),\n privateTypeIndex.doc()\n ),\n st(\n reg,\n ns.solid('forClass'),\n ns.meeting('LongChat'),\n privateTypeIndex.doc()\n ),\n st(reg, ns.solid('instance'), chatThing, privateTypeIndex.doc()),\n ]\n await new Promise((resolve, reject) => {\n store.updater.update([], ins, function (_uri, ok, errm) {\n if (!ok) {\n reject(new Error(errm))\n } else {\n resolve(null)\n }\n })\n })\n }\n\n async function findChat(invitee: NamedNode): Promise<Chat> {\n const me = await profileLogic.loadMe()\n const podRoot = await profileLogic.getPodRoot(me)\n const chatContainer = determineChatContainer(invitee, podRoot)\n let exists = true\n try {\n await store.fetcher.load(\n new NamedNode(chatContainer.value + 'index.ttl#this')\n )\n } catch (e) {\n exists = false\n }\n return { me, chatContainer, exists }\n }\n\n async function createChatThing(\n chatContainer: NamedNode,\n me: NamedNode\n ): Promise<NamedNode> {\n const created = await mintNew({\n me,\n newBase: chatContainer.value,\n })\n return created.newInstance\n }\n\n function mintNew(newPaneOptions: NewPaneOptions): Promise<CreatedPaneOptions> {\n const kb = store\n const updater = kb.updater\n if (newPaneOptions.me && !newPaneOptions.me.uri) {\n throw new Error('chat mintNew: Invalid userid ' + newPaneOptions.me)\n }\n\n const newInstance = (newPaneOptions.newInstance =\n newPaneOptions.newInstance ||\n kb.sym(newPaneOptions.newBase + CHAT_LOCATION_IN_CONTAINER))\n const newChatDoc = newInstance.doc()\n\n kb.add(\n newInstance,\n ns.rdf('type'),\n ns.meeting('LongChat'),\n newChatDoc\n )\n kb.add(newInstance, ns.dc('title'), 'Chat channel', newChatDoc)\n kb.add(\n newInstance,\n ns.dc('created'),\n term<Node>(new Date(Date.now())),\n newChatDoc\n )\n if (newPaneOptions.me) {\n kb.add(newInstance, ns.dc('author'), newPaneOptions.me, newChatDoc)\n }\n\n return new Promise(function (resolve, reject) {\n updater?.put(\n newChatDoc,\n kb.statementsMatching(undefined, undefined, undefined, newChatDoc),\n 'text/turtle',\n function (uri2, ok, message) {\n if (ok) {\n resolve({\n ...newPaneOptions,\n newInstance,\n })\n } else {\n reject(\n new Error(\n 'FAILED to save new chat channel at: ' + uri2 + ' : ' + message\n )\n )\n }\n }\n )\n })\n }\n\n /**\n * Find (and optionally create) an individual chat between the current user and the given invitee\n * @param invitee - The person to chat with\n * @param createIfMissing - Whether the chat should be created, if missing\n * @returns null if missing, or a node referring to an already existing chat, or the newly created chat\n */\n async function getChat(\n invitee: NamedNode,\n createIfMissing = true\n ): Promise<NamedNode | null> {\n const { me, chatContainer, exists } = await findChat(invitee)\n if (exists) {\n return new NamedNode(chatContainer.value + CHAT_LOCATION_IN_CONTAINER)\n }\n\n if (createIfMissing) {\n const chatThing = await createChatThing(chatContainer, me)\n await sendInvite(invitee, chatThing)\n await setAcl(chatContainer, me, invitee)\n await addToPrivateTypeIndex(chatThing, me)\n return chatThing\n }\n return null\n }\n\n async function sendInvite(invitee: NamedNode, chatThing: NamedNode) {\n await store.fetcher.load(invitee.doc())\n const inviteeInbox = store.any(\n invitee,\n ns.ldp('inbox'),\n undefined,\n invitee.doc()\n )\n if (!inviteeInbox) {\n throw new Error(`Invitee inbox not found! ${invitee.value}`)\n }\n const inviteBody = `\n <> a <http://www.w3.org/ns/pim/meeting#LongChatInvite> ;\n ${ns.rdf('seeAlso')} <${chatThing.value}> .\n `\n\n const inviteResponse = await store.fetcher?.webOperation(\n 'POST',\n inviteeInbox.value,\n {\n data: inviteBody,\n contentType: 'text/turtle',\n }\n )\n const locationStr = inviteResponse?.headers.get('location')\n if (!locationStr) {\n throw new Error(`Invite sending returned a ${inviteResponse?.status}`)\n }\n }\n return {\n setAcl, addToPrivateTypeIndex, findChat, createChatThing, getChat, sendInvite, mintNew\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { ChatLogic } from '../types';
2
+ export declare function createChatLogic(store: any, profileLogic: any): ChatLogic;
3
+ //# sourceMappingURL=chatLogic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatLogic.d.ts","sourceRoot":"","sources":["../../src/chat/chatLogic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAA4C,MAAM,UAAU,CAAA;AAM9E,wBAAgB,eAAe,CAAC,KAAK,KAAA,EAAE,YAAY,KAAA,GAAG,SAAS,CAyN9D"}
@@ -47,16 +47,16 @@ function s(s, c) {
47
47
  });
48
48
  }
49
49
  async function f(e) {
50
- let n = await c.loadMe(), i = t(e, await c.getPodRoot(n)), a = !0;
50
+ let n = await c.loadMe(), i = await c.getPodRoot(n), a = t(e, i), o = !0;
51
51
  try {
52
- await s.fetcher.load(new r(i.value + "index.ttl#this"));
52
+ await s.fetcher.load(new r(a.value + "index.ttl#this"));
53
53
  } catch {
54
- a = !1;
54
+ o = !1;
55
55
  }
56
56
  return {
57
57
  me: n,
58
- chatContainer: i,
59
- exists: a
58
+ chatContainer: a,
59
+ exists: o
60
60
  };
61
61
  }
62
62
  async function p(e, t) {
@@ -1 +1 @@
1
- {"version":3,"file":"chatLogic.esm.js","names":[],"sources":["../../src/chat/chatLogic.ts"],"sourcesContent":["import { NamedNode, Node, st, term } from 'rdflib'\nimport { ChatLogic, CreatedPaneOptions, NewPaneOptions, Chat } from '../types'\nimport { ns as namespace } from '../util/ns'\nimport { determineChatContainer, newThing } from '../util/utils'\n\nconst CHAT_LOCATION_IN_CONTAINER = 'index.ttl#this'\n\nexport function createChatLogic(store, profileLogic): ChatLogic {\n const ns = namespace\n\n async function setAcl(\n chatContainer: NamedNode,\n me: NamedNode,\n invitee: NamedNode\n ): Promise<void> {\n // Some servers don't present a Link http response header\n // if the container doesn't exist yet, so refetch the container\n // now that it has been created:\n await store.fetcher.load(chatContainer)\n\n // FIXME: check the Why value on this quad:\n const chatAclDoc = store.any(\n chatContainer,\n new NamedNode('http://www.iana.org/assignments/link-relations/acl')\n )\n if (!chatAclDoc) {\n throw new Error('Chat ACL doc not found!')\n }\n\n const aclBody = `\n @prefix acl: <http://www.w3.org/ns/auth/acl#>.\n <#owner>\n a acl:Authorization;\n acl:agent <${me.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Write, acl:Control.\n <#invitee>\n a acl:Authorization;\n acl:agent <${invitee.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Append.\n `\n await store.fetcher.webOperation('PUT', chatAclDoc.value, {\n data: aclBody,\n contentType: 'text/turtle',\n })\n }\n\n async function addToPrivateTypeIndex(chatThing, me) {\n // Add to private type index\n const privateTypeIndex = store.any(\n me,\n ns.solid('privateTypeIndex')\n ) as NamedNode | null\n if (!privateTypeIndex) {\n throw new Error('Private type index not found!')\n }\n await store.fetcher.load(privateTypeIndex)\n const reg = newThing(privateTypeIndex)\n const ins = [\n st(\n reg,\n ns.rdf('type'),\n ns.solid('TypeRegistration'),\n privateTypeIndex.doc()\n ),\n st(\n reg,\n ns.solid('forClass'),\n ns.meeting('LongChat'),\n privateTypeIndex.doc()\n ),\n st(reg, ns.solid('instance'), chatThing, privateTypeIndex.doc()),\n ]\n await new Promise((resolve, reject) => {\n store.updater.update([], ins, function (_uri, ok, errm) {\n if (!ok) {\n reject(new Error(errm))\n } else {\n resolve(null)\n }\n })\n })\n }\n\n async function findChat(invitee: NamedNode): Promise<Chat> {\n const me = await profileLogic.loadMe()\n const podRoot = await profileLogic.getPodRoot(me)\n const chatContainer = determineChatContainer(invitee, podRoot)\n let exists = true\n try {\n await store.fetcher.load(\n new NamedNode(chatContainer.value + 'index.ttl#this')\n )\n } catch (e) {\n exists = false\n }\n return { me, chatContainer, exists }\n }\n\n async function createChatThing(\n chatContainer: NamedNode,\n me: NamedNode\n ): Promise<NamedNode> {\n const created = await mintNew({\n me,\n newBase: chatContainer.value,\n })\n return created.newInstance\n }\n\n function mintNew(newPaneOptions: NewPaneOptions): Promise<CreatedPaneOptions> {\n const kb = store\n const updater = kb.updater\n if (newPaneOptions.me && !newPaneOptions.me.uri) {\n throw new Error('chat mintNew: Invalid userid ' + newPaneOptions.me)\n }\n\n const newInstance = (newPaneOptions.newInstance =\n newPaneOptions.newInstance ||\n kb.sym(newPaneOptions.newBase + CHAT_LOCATION_IN_CONTAINER))\n const newChatDoc = newInstance.doc()\n\n kb.add(\n newInstance,\n ns.rdf('type'),\n ns.meeting('LongChat'),\n newChatDoc\n )\n kb.add(newInstance, ns.dc('title'), 'Chat channel', newChatDoc)\n kb.add(\n newInstance,\n ns.dc('created'),\n term<Node>(new Date(Date.now())),\n newChatDoc\n )\n if (newPaneOptions.me) {\n kb.add(newInstance, ns.dc('author'), newPaneOptions.me, newChatDoc)\n }\n\n return new Promise(function (resolve, reject) {\n updater?.put(\n newChatDoc,\n kb.statementsMatching(undefined, undefined, undefined, newChatDoc),\n 'text/turtle',\n function (uri2, ok, message) {\n if (ok) {\n resolve({\n ...newPaneOptions,\n newInstance,\n })\n } else {\n reject(\n new Error(\n 'FAILED to save new chat channel at: ' + uri2 + ' : ' + message\n )\n )\n }\n }\n )\n })\n }\n\n /**\n * Find (and optionally create) an individual chat between the current user and the given invitee\n * @param invitee - The person to chat with\n * @param createIfMissing - Whether the chat should be created, if missing\n * @returns null if missing, or a node referring to an already existing chat, or the newly created chat\n */\n async function getChat(\n invitee: NamedNode,\n createIfMissing = true\n ): Promise<NamedNode | null> {\n const { me, chatContainer, exists } = await findChat(invitee)\n if (exists) {\n return new NamedNode(chatContainer.value + CHAT_LOCATION_IN_CONTAINER)\n }\n\n if (createIfMissing) {\n const chatThing = await createChatThing(chatContainer, me)\n await sendInvite(invitee, chatThing)\n await setAcl(chatContainer, me, invitee)\n await addToPrivateTypeIndex(chatThing, me)\n return chatThing\n }\n return null\n }\n\n async function sendInvite(invitee: NamedNode, chatThing: NamedNode) {\n await store.fetcher.load(invitee.doc())\n const inviteeInbox = store.any(\n invitee,\n ns.ldp('inbox'),\n undefined,\n invitee.doc()\n )\n if (!inviteeInbox) {\n throw new Error(`Invitee inbox not found! ${invitee.value}`)\n }\n const inviteBody = `\n <> a <http://www.w3.org/ns/pim/meeting#LongChatInvite> ;\n ${ns.rdf('seeAlso')} <${chatThing.value}> .\n `\n\n const inviteResponse = await store.fetcher?.webOperation(\n 'POST',\n inviteeInbox.value,\n {\n data: inviteBody,\n contentType: 'text/turtle',\n }\n )\n const locationStr = inviteResponse?.headers.get('location')\n if (!locationStr) {\n throw new Error(`Invite sending returned a ${inviteResponse?.status}`)\n }\n }\n return {\n setAcl, addToPrivateTypeIndex, findChat, createChatThing, getChat, sendInvite, mintNew\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"chatLogic.esm.js","names":[],"sources":["../../src/chat/chatLogic.ts"],"sourcesContent":["import { NamedNode, Node, st, term } from 'rdflib'\nimport { ChatLogic, CreatedPaneOptions, NewPaneOptions, Chat } from '../types'\nimport { ns as namespace } from '../util/ns'\nimport { determineChatContainer, newThing } from '../util/utils'\n\nconst CHAT_LOCATION_IN_CONTAINER = 'index.ttl#this'\n\nexport function createChatLogic(store, profileLogic): ChatLogic {\n const ns = namespace\n\n async function setAcl(\n chatContainer: NamedNode,\n me: NamedNode,\n invitee: NamedNode\n ): Promise<void> {\n // Some servers don't present a Link http response header\n // if the container doesn't exist yet, so refetch the container\n // now that it has been created:\n await store.fetcher.load(chatContainer)\n\n // FIXME: check the Why value on this quad:\n const chatAclDoc = store.any(\n chatContainer,\n new NamedNode('http://www.iana.org/assignments/link-relations/acl')\n )\n if (!chatAclDoc) {\n throw new Error('Chat ACL doc not found!')\n }\n\n const aclBody = `\n @prefix acl: <http://www.w3.org/ns/auth/acl#>.\n <#owner>\n a acl:Authorization;\n acl:agent <${me.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Write, acl:Control.\n <#invitee>\n a acl:Authorization;\n acl:agent <${invitee.value}>;\n acl:accessTo <.>;\n acl:default <.>;\n acl:mode\n acl:Read, acl:Append.\n `\n await store.fetcher.webOperation('PUT', chatAclDoc.value, {\n data: aclBody,\n contentType: 'text/turtle',\n })\n }\n\n async function addToPrivateTypeIndex(chatThing, me) {\n // Add to private type index\n const privateTypeIndex = store.any(\n me,\n ns.solid('privateTypeIndex')\n ) as NamedNode | null\n if (!privateTypeIndex) {\n throw new Error('Private type index not found!')\n }\n await store.fetcher.load(privateTypeIndex)\n const reg = newThing(privateTypeIndex)\n const ins = [\n st(\n reg,\n ns.rdf('type'),\n ns.solid('TypeRegistration'),\n privateTypeIndex.doc()\n ),\n st(\n reg,\n ns.solid('forClass'),\n ns.meeting('LongChat'),\n privateTypeIndex.doc()\n ),\n st(reg, ns.solid('instance'), chatThing, privateTypeIndex.doc()),\n ]\n await new Promise((resolve, reject) => {\n store.updater.update([], ins, function (_uri, ok, errm) {\n if (!ok) {\n reject(new Error(errm))\n } else {\n resolve(null)\n }\n })\n })\n }\n\n async function findChat(invitee: NamedNode): Promise<Chat> {\n const me = await profileLogic.loadMe()\n const podRoot = await profileLogic.getPodRoot(me)\n const chatContainer = determineChatContainer(invitee, podRoot)\n let exists = true\n try {\n await store.fetcher.load(\n new NamedNode(chatContainer.value + 'index.ttl#this')\n )\n } catch (e) {\n exists = false\n }\n return { me, chatContainer, exists }\n }\n\n async function createChatThing(\n chatContainer: NamedNode,\n me: NamedNode\n ): Promise<NamedNode> {\n const created = await mintNew({\n me,\n newBase: chatContainer.value,\n })\n return created.newInstance\n }\n\n function mintNew(newPaneOptions: NewPaneOptions): Promise<CreatedPaneOptions> {\n const kb = store\n const updater = kb.updater\n if (newPaneOptions.me && !newPaneOptions.me.uri) {\n throw new Error('chat mintNew: Invalid userid ' + newPaneOptions.me)\n }\n\n const newInstance = (newPaneOptions.newInstance =\n newPaneOptions.newInstance ||\n kb.sym(newPaneOptions.newBase + CHAT_LOCATION_IN_CONTAINER))\n const newChatDoc = newInstance.doc()\n\n kb.add(\n newInstance,\n ns.rdf('type'),\n ns.meeting('LongChat'),\n newChatDoc\n )\n kb.add(newInstance, ns.dc('title'), 'Chat channel', newChatDoc)\n kb.add(\n newInstance,\n ns.dc('created'),\n term<Node>(new Date(Date.now())),\n newChatDoc\n )\n if (newPaneOptions.me) {\n kb.add(newInstance, ns.dc('author'), newPaneOptions.me, newChatDoc)\n }\n\n return new Promise(function (resolve, reject) {\n updater?.put(\n newChatDoc,\n kb.statementsMatching(undefined, undefined, undefined, newChatDoc),\n 'text/turtle',\n function (uri2, ok, message) {\n if (ok) {\n resolve({\n ...newPaneOptions,\n newInstance,\n })\n } else {\n reject(\n new Error(\n 'FAILED to save new chat channel at: ' + uri2 + ' : ' + message\n )\n )\n }\n }\n )\n })\n }\n\n /**\n * Find (and optionally create) an individual chat between the current user and the given invitee\n * @param invitee - The person to chat with\n * @param createIfMissing - Whether the chat should be created, if missing\n * @returns null if missing, or a node referring to an already existing chat, or the newly created chat\n */\n async function getChat(\n invitee: NamedNode,\n createIfMissing = true\n ): Promise<NamedNode | null> {\n const { me, chatContainer, exists } = await findChat(invitee)\n if (exists) {\n return new NamedNode(chatContainer.value + CHAT_LOCATION_IN_CONTAINER)\n }\n\n if (createIfMissing) {\n const chatThing = await createChatThing(chatContainer, me)\n await sendInvite(invitee, chatThing)\n await setAcl(chatContainer, me, invitee)\n await addToPrivateTypeIndex(chatThing, me)\n return chatThing\n }\n return null\n }\n\n async function sendInvite(invitee: NamedNode, chatThing: NamedNode) {\n await store.fetcher.load(invitee.doc())\n const inviteeInbox = store.any(\n invitee,\n ns.ldp('inbox'),\n undefined,\n invitee.doc()\n )\n if (!inviteeInbox) {\n throw new Error(`Invitee inbox not found! ${invitee.value}`)\n }\n const inviteBody = `\n <> a <http://www.w3.org/ns/pim/meeting#LongChatInvite> ;\n ${ns.rdf('seeAlso')} <${chatThing.value}> .\n `\n\n const inviteResponse = await store.fetcher?.webOperation(\n 'POST',\n inviteeInbox.value,\n {\n data: inviteBody,\n contentType: 'text/turtle',\n }\n )\n const locationStr = inviteResponse?.headers.get('location')\n if (!locationStr) {\n throw new Error(`Invite sending returned a ${inviteResponse?.status}`)\n }\n }\n return {\n setAcl, addToPrivateTypeIndex, findChat, createChatThing, getChat, sendInvite, mintNew\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../util/debug.cjs.js"),t=require("../util/ns.cjs.js"),n=require("./directoryHelpers.cjs.js");let r=require("rdflib");var i=`http://example.org#`;function a(){let a=new Map;async function o(e,t){let n=await s(e);for(let e of n)await t(e)}async function s(t){let n=a.get(t);if(n)return n;let r=c(t).catch(e=>{throw a.delete(t),e});a.set(t,r);try{return await r}catch(t){return e.warn(`[Directory] Error fetching directory catalog:`,t),[]}}async function c(e){if(typeof fetch!=`function`)throw Error(`Fetch API is unavailable`);let a=await fetch(e,{headers:{accept:`text/turtle`}});if(!a.ok)throw Error(`Failed to fetch ${e}: ${a.status}`);let o=await a.text(),s=(0,r.graph)();(0,r.parse)(o,s,e,`text/turtle`);let c=new r.NamedNode(`${i}Person`),l=new r.NamedNode(`${i}webid`),u=new r.NamedNode(`${i}name`),d=new Map,f=s.statementsMatching(void 0,t.ns.rdf(`type`),c);for(let e of f){let t=e.subject,r=s.any(t,l);if(r&&r.termType===`NamedNode`){let e=r.value,i=s.anyValue(t,u);n.isHttpUri(e)&&i&&d.set(e,{kind:`person`,uri:e,label:i,subjectType:`agent`,relationshipLabel:`People`,sources:[`catalog`]})}}return Array.from(d.values())}return{discoverCatalogEntries:o}}exports.createCatalogDirectoryDiscovery=a;
2
+ //# sourceMappingURL=catalogDirectory.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogDirectory.cjs.js","names":[],"sources":["../../src/directory/catalogDirectory.ts"],"sourcesContent":["import { type DirectoryEntry } from '../types'\nimport { graph, type LiveStore, NamedNode, parse } from 'rdflib'\nimport { ns } from '../util/ns'\nimport * as debug from '../util/debug'\nimport { isHttpUri } from './directoryHelpers'\n\nconst CATALOG_VOCAB = 'http://example.org#'\n\nexport function createCatalogDirectoryDiscovery () {\n const catalogEntriesCache = new Map<string, Promise<DirectoryEntry[]>>()\n\n async function discoverCatalogEntries (\n catalogUrl: string,\n onEntry: (entry: DirectoryEntry) => void | Promise<void>\n ): Promise<void> {\n const entries = await fetchCatalogEntries(catalogUrl)\n for (const entry of entries) {\n await onEntry(entry)\n }\n }\n\n async function fetchCatalogEntries (catalogUrl: string): Promise<DirectoryEntry[]> {\n const cached = catalogEntriesCache.get(catalogUrl)\n if (cached) {\n return cached\n }\n\n const promise = loadCatalogEntries(catalogUrl).catch(error => {\n catalogEntriesCache.delete(catalogUrl)\n throw error\n })\n\n catalogEntriesCache.set(catalogUrl, promise)\n try {\n return await promise\n } catch (error) {\n debug.warn('[Directory] Error fetching directory catalog:', error)\n return []\n }\n }\n\n async function loadCatalogEntries (catalogUrl: string): Promise<DirectoryEntry[]> {\n if (typeof fetch !== 'function') {\n throw new Error('Fetch API is unavailable')\n }\n\n const response = await fetch(catalogUrl, {\n headers: { accept: 'text/turtle' }\n })\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${catalogUrl}: ${response.status}`)\n }\n\n const turtle = await response.text()\n const catalogStore = graph() as LiveStore\n parse(turtle, catalogStore, catalogUrl, 'text/turtle')\n\n const personType = new NamedNode(`${CATALOG_VOCAB}Person`)\n const webIdPredicate = new NamedNode(`${CATALOG_VOCAB}webid`)\n const namePredicate = new NamedNode(`${CATALOG_VOCAB}name`)\n const catalogPeople = new Map<string, DirectoryEntry>()\n\n const statements = catalogStore.statementsMatching(undefined, ns.rdf('type'), personType)\n for (const statement of statements) {\n const subject = statement.subject\n const webIdNode = catalogStore.any(subject, webIdPredicate)\n if (webIdNode && webIdNode.termType === 'NamedNode') {\n const webId = webIdNode.value\n const personName = catalogStore.anyValue(subject, namePredicate)\n if (isHttpUri(webId) && personName) {\n catalogPeople.set(webId, {\n kind: 'person',\n uri: webId,\n label: personName,\n subjectType: 'agent',\n relationshipLabel: 'People',\n sources: ['catalog']\n })\n }\n }\n }\n\n return Array.from(catalogPeople.values())\n }\n\n return {\n discoverCatalogEntries\n }\n}\n"],"mappings":"sIAMA,IAAM,EAAgB,sBAEtB,SAAgB,GAAmC,CACjD,IAAM,EAAsB,IAAI,IAEhC,eAAe,EACb,EACA,EACe,CACf,IAAM,EAAU,MAAM,EAAoB,CAAU,EACpD,IAAK,IAAM,KAAS,EAClB,MAAM,EAAQ,CAAK,CAEvB,CAEA,eAAe,EAAqB,EAA+C,CACjF,IAAM,EAAS,EAAoB,IAAI,CAAU,EACjD,GAAI,EACF,OAAO,EAGT,IAAM,EAAU,EAAmB,CAAU,CAAC,CAAC,MAAM,GAAS,CAE5D,MADA,EAAoB,OAAO,CAAU,EAC/B,CACR,CAAC,EAED,EAAoB,IAAI,EAAY,CAAO,EAC3C,GAAI,CACF,OAAO,MAAM,CACf,OAAS,EAAO,CAEd,OADA,EAAA,KAAW,gDAAiD,CAAK,EAC1D,CAAC,CACV,CACF,CAEA,eAAe,EAAoB,EAA+C,CAChF,GAAI,OAAO,OAAU,WACnB,MAAU,MAAM,0BAA0B,EAG5C,IAAM,EAAW,MAAM,MAAM,EAAY,CACvC,QAAS,CAAE,OAAQ,aAAc,CACnC,CAAC,EAED,GAAI,CAAC,EAAS,GACZ,MAAU,MAAM,mBAAmB,EAAW,IAAI,EAAS,QAAQ,EAGrE,IAAM,EAAS,MAAM,EAAS,KAAK,EAC7B,GAAA,EAAe,EAAA,MAAA,CAAM,GAC3B,EAAA,EAAA,MAAA,CAAM,EAAQ,EAAc,EAAY,aAAa,EAErD,IAAM,EAAa,IAAI,EAAA,UAAU,GAAG,EAAc,OAAO,EACnD,EAAiB,IAAI,EAAA,UAAU,GAAG,EAAc,MAAM,EACtD,EAAgB,IAAI,EAAA,UAAU,GAAG,EAAc,KAAK,EACpD,EAAgB,IAAI,IAEpB,EAAa,EAAa,mBAAmB,IAAA,GAAW,EAAA,GAAG,IAAI,MAAM,EAAG,CAAU,EACxF,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAU,EAAU,QACpB,EAAY,EAAa,IAAI,EAAS,CAAc,EAC1D,GAAI,GAAa,EAAU,WAAa,YAAa,CACnD,IAAM,EAAQ,EAAU,MAClB,EAAa,EAAa,SAAS,EAAS,CAAa,EAC3D,EAAA,UAAU,CAAK,GAAK,GACtB,EAAc,IAAI,EAAO,CACvB,KAAM,SACN,IAAK,EACL,MAAO,EACP,YAAa,QACb,kBAAmB,SACnB,QAAS,CAAC,SAAS,CACrB,CAAC,CAEL,CACF,CAEA,OAAO,MAAM,KAAK,EAAc,OAAO,CAAC,CAC1C,CAEA,MAAO,CACL,wBACF,CACF"}
@@ -0,0 +1,5 @@
1
+ import { type DirectoryEntry } from '../types';
2
+ export declare function createCatalogDirectoryDiscovery(): {
3
+ discoverCatalogEntries: (catalogUrl: string, onEntry: (entry: DirectoryEntry) => void | Promise<void>) => Promise<void>;
4
+ };
5
+ //# sourceMappingURL=catalogDirectory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogDirectory.d.ts","sourceRoot":"","sources":["../../src/directory/catalogDirectory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAQ9C,wBAAgB,+BAA+B;yCAI/B,MAAM,WACT,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KACvD,OAAO,CAAC,IAAI,CAAC;EA2EjB"}
@@ -0,0 +1,54 @@
1
+ import { warn as e } from "../util/debug.esm.js";
2
+ import { ns as t } from "../util/ns.esm.js";
3
+ import { isHttpUri as n } from "./directoryHelpers.esm.js";
4
+ import { NamedNode as r, graph as i, parse as a } from "rdflib";
5
+ //#region src/directory/catalogDirectory.ts
6
+ var o = "http://example.org#";
7
+ function s() {
8
+ let s = /* @__PURE__ */ new Map();
9
+ async function c(e, t) {
10
+ let n = await l(e);
11
+ for (let e of n) await t(e);
12
+ }
13
+ async function l(t) {
14
+ let n = s.get(t);
15
+ if (n) return n;
16
+ let r = u(t).catch((e) => {
17
+ throw s.delete(t), e;
18
+ });
19
+ s.set(t, r);
20
+ try {
21
+ return await r;
22
+ } catch (t) {
23
+ return e("[Directory] Error fetching directory catalog:", t), [];
24
+ }
25
+ }
26
+ async function u(e) {
27
+ if (typeof fetch != "function") throw Error("Fetch API is unavailable");
28
+ let s = await fetch(e, { headers: { accept: "text/turtle" } });
29
+ if (!s.ok) throw Error(`Failed to fetch ${e}: ${s.status}`);
30
+ let c = await s.text(), l = i();
31
+ a(c, l, e, "text/turtle");
32
+ let u = new r(`${o}Person`), d = new r(`${o}webid`), f = new r(`${o}name`), p = /* @__PURE__ */ new Map(), m = l.statementsMatching(void 0, t.rdf("type"), u);
33
+ for (let e of m) {
34
+ let t = e.subject, r = l.any(t, d);
35
+ if (r && r.termType === "NamedNode") {
36
+ let e = r.value, i = l.anyValue(t, f);
37
+ n(e) && i && p.set(e, {
38
+ kind: "person",
39
+ uri: e,
40
+ label: i,
41
+ subjectType: "agent",
42
+ relationshipLabel: "People",
43
+ sources: ["catalog"]
44
+ });
45
+ }
46
+ }
47
+ return Array.from(p.values());
48
+ }
49
+ return { discoverCatalogEntries: c };
50
+ }
51
+ //#endregion
52
+ export { s as createCatalogDirectoryDiscovery };
53
+
54
+ //# sourceMappingURL=catalogDirectory.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogDirectory.esm.js","names":[],"sources":["../../src/directory/catalogDirectory.ts"],"sourcesContent":["import { type DirectoryEntry } from '../types'\nimport { graph, type LiveStore, NamedNode, parse } from 'rdflib'\nimport { ns } from '../util/ns'\nimport * as debug from '../util/debug'\nimport { isHttpUri } from './directoryHelpers'\n\nconst CATALOG_VOCAB = 'http://example.org#'\n\nexport function createCatalogDirectoryDiscovery () {\n const catalogEntriesCache = new Map<string, Promise<DirectoryEntry[]>>()\n\n async function discoverCatalogEntries (\n catalogUrl: string,\n onEntry: (entry: DirectoryEntry) => void | Promise<void>\n ): Promise<void> {\n const entries = await fetchCatalogEntries(catalogUrl)\n for (const entry of entries) {\n await onEntry(entry)\n }\n }\n\n async function fetchCatalogEntries (catalogUrl: string): Promise<DirectoryEntry[]> {\n const cached = catalogEntriesCache.get(catalogUrl)\n if (cached) {\n return cached\n }\n\n const promise = loadCatalogEntries(catalogUrl).catch(error => {\n catalogEntriesCache.delete(catalogUrl)\n throw error\n })\n\n catalogEntriesCache.set(catalogUrl, promise)\n try {\n return await promise\n } catch (error) {\n debug.warn('[Directory] Error fetching directory catalog:', error)\n return []\n }\n }\n\n async function loadCatalogEntries (catalogUrl: string): Promise<DirectoryEntry[]> {\n if (typeof fetch !== 'function') {\n throw new Error('Fetch API is unavailable')\n }\n\n const response = await fetch(catalogUrl, {\n headers: { accept: 'text/turtle' }\n })\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${catalogUrl}: ${response.status}`)\n }\n\n const turtle = await response.text()\n const catalogStore = graph() as LiveStore\n parse(turtle, catalogStore, catalogUrl, 'text/turtle')\n\n const personType = new NamedNode(`${CATALOG_VOCAB}Person`)\n const webIdPredicate = new NamedNode(`${CATALOG_VOCAB}webid`)\n const namePredicate = new NamedNode(`${CATALOG_VOCAB}name`)\n const catalogPeople = new Map<string, DirectoryEntry>()\n\n const statements = catalogStore.statementsMatching(undefined, ns.rdf('type'), personType)\n for (const statement of statements) {\n const subject = statement.subject\n const webIdNode = catalogStore.any(subject, webIdPredicate)\n if (webIdNode && webIdNode.termType === 'NamedNode') {\n const webId = webIdNode.value\n const personName = catalogStore.anyValue(subject, namePredicate)\n if (isHttpUri(webId) && personName) {\n catalogPeople.set(webId, {\n kind: 'person',\n uri: webId,\n label: personName,\n subjectType: 'agent',\n relationshipLabel: 'People',\n sources: ['catalog']\n })\n }\n }\n }\n\n return Array.from(catalogPeople.values())\n }\n\n return {\n discoverCatalogEntries\n }\n}\n"],"mappings":";;;;;AAMA,IAAM,IAAgB;AAEtB,SAAgB,IAAmC;CACjD,IAAM,oBAAsB,IAAI,IAAuC;CAEvE,eAAe,EACb,GACA,GACe;EACf,IAAM,IAAU,MAAM,EAAoB,CAAU;EACpD,KAAK,IAAM,KAAS,GAClB,MAAM,EAAQ,CAAK;CAEvB;CAEA,eAAe,EAAqB,GAA+C;EACjF,IAAM,IAAS,EAAoB,IAAI,CAAU;EACjD,IAAI,GACF,OAAO;EAGT,IAAM,IAAU,EAAmB,CAAU,CAAC,CAAC,OAAM,MAAS;GAE5D,MADA,EAAoB,OAAO,CAAU,GAC/B;EACR,CAAC;EAED,EAAoB,IAAI,GAAY,CAAO;EAC3C,IAAI;GACF,OAAO,MAAM;EACf,SAAS,GAAO;GAEd,OADA,EAAW,iDAAiD,CAAK,GAC1D,CAAC;EACV;CACF;CAEA,eAAe,EAAoB,GAA+C;EAChF,IAAI,OAAO,SAAU,YACnB,MAAU,MAAM,0BAA0B;EAG5C,IAAM,IAAW,MAAM,MAAM,GAAY,EACvC,SAAS,EAAE,QAAQ,cAAc,EACnC,CAAC;EAED,IAAI,CAAC,EAAS,IACZ,MAAU,MAAM,mBAAmB,EAAW,IAAI,EAAS,QAAQ;EAGrE,IAAM,IAAS,MAAM,EAAS,KAAK,GAC7B,IAAe,EAAM;EAC3B,EAAM,GAAQ,GAAc,GAAY,aAAa;EAErD,IAAM,IAAa,IAAI,EAAU,GAAG,EAAc,OAAO,GACnD,IAAiB,IAAI,EAAU,GAAG,EAAc,MAAM,GACtD,IAAgB,IAAI,EAAU,GAAG,EAAc,KAAK,GACpD,oBAAgB,IAAI,IAA4B,GAEhD,IAAa,EAAa,mBAAmB,KAAA,GAAW,EAAG,IAAI,MAAM,GAAG,CAAU;EACxF,KAAK,IAAM,KAAa,GAAY;GAClC,IAAM,IAAU,EAAU,SACpB,IAAY,EAAa,IAAI,GAAS,CAAc;GAC1D,IAAI,KAAa,EAAU,aAAa,aAAa;IACnD,IAAM,IAAQ,EAAU,OAClB,IAAa,EAAa,SAAS,GAAS,CAAa;IAC/D,AAAI,EAAU,CAAK,KAAK,KACtB,EAAc,IAAI,GAAO;KACvB,MAAM;KACN,KAAK;KACL,OAAO;KACP,aAAa;KACb,mBAAmB;KACnB,SAAS,CAAC,SAAS;IACrB,CAAC;GAEL;EACF;EAEA,OAAO,MAAM,KAAK,EAAc,OAAO,CAAC;CAC1C;CAEA,OAAO,EACL,0BACF;AACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../util/ns.cjs.js"),t=require("./directoryHelpers.cjs.js");var n=8;function r(r,i){let a=new Map,o=new Map;async function s(n){let r=a.get(n.value);if(r)return r;let o=(async()=>{let r=await i.loadTypeIndexesFor(n),a=[];for(let t of r){let n=await i.getScopedAppsFromIndex(t,e.ns.vcard(`AddressBook`));a=a.concat(n)}return t.uniqueNamedNodes(a.map(e=>e.instance))})().catch(e=>{throw a.delete(n.value),e});return a.set(n.value,o),o}async function c(n){let i=o.get(n.value);if(i)return i;let a=(async()=>{if(!await t.loadDocumentSilently(r,n))return null;let i=r.each(n,e.ns.vcard(`url`),null,n.doc());for(let e of i){let t=l(e,n.doc());if(t)return t}return null})().catch(e=>{throw o.delete(n.value),e});return o.set(n.value,a),a}function l(n,i){if(!r.holds(n,e.ns.rdf(`type`),e.ns.vcard(`WebID`),i))return null;let a=r.any(n,e.ns.vcard(`value`),null,i)?.value||``;return t.isHttpUri(a)?a:null}async function u(i,a){let o=await s(i);for(let i of o)if(await t.loadDocumentSilently(r,i)){let o=r.any(i,e.ns.vcard(`nameEmailIndex`),null,i.doc());if(o&&await t.loadDocumentSilently(r,o)){let s=r.each(void 0,e.ns.vcard(`inAddressBook`),i,o);for(let i=0;i<s.length;i+=n){let l=s.slice(i,i+n),u=await Promise.all(l.map(async n=>{let i=await c(n);return i?{kind:`person`,uri:i,label:r.anyValue(n,e.ns.vcard(`fn`),null,o)||t.labelForNode(r,n),subjectType:`agent`,relationshipLabel:`Contact`,sources:[`contacts`]}:null}));for(let e of u.filter(e=>!!e))await a(e)}}}}async function d(n,i){let a=await s(n);for(let n of a)if(await t.loadDocumentSilently(r,n)){let a=r.any(n,e.ns.vcard(`groupIndex`),null,n.doc());if(a&&await t.loadDocumentSilently(r,a)){let o=t.uniqueNamedNodes([...r.each(n,e.ns.vcard(`includesGroup`),null,a),...r.each(n,e.ns.vcard(`includesGroup`),null,n.doc())]);for(let e of o)await t.loadDocumentSilently(r,e),await i({kind:`group`,uri:e.value,label:t.labelForNode(r,e),subjectType:`agentGroup`,relationshipLabel:`Group`,sources:[`groups`]})}}}return{discoverAddressBookContacts:u,discoverAddressBookGroups:d}}exports.createContactsDirectoryDiscovery=r;
2
+ //# sourceMappingURL=contactsDirectory.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contactsDirectory.cjs.js","names":[],"sources":["../../src/directory/contactsDirectory.ts"],"sourcesContent":["import { type DirectoryEntry, type DirectorySource, type TypeIndexLogic } from '../types'\nimport { type LiveStore, NamedNode } from 'rdflib'\nimport { ns } from '../util/ns'\nimport { isHttpUri, labelForNode, loadDocumentSilently, uniqueNamedNodes } from './directoryHelpers'\n\nconst CONTACT_CARD_CONCURRENCY = 8\n\nexport function createContactsDirectoryDiscovery (\n store: LiveStore,\n typeIndexLogic: TypeIndexLogic\n) {\n const personalAddressBooksCache = new Map<string, Promise<NamedNode[]>>()\n const contactWebIdCache = new Map<string, Promise<string | null>>()\n\n async function loadPersonalAddressBooks (user: NamedNode): Promise<NamedNode[]> {\n const cached = personalAddressBooksCache.get(user.value)\n if (cached) {\n return cached\n }\n\n const promise = (async () => {\n const scopes = await typeIndexLogic.loadTypeIndexesFor(user)\n let scopedApps = [] as Awaited<ReturnType<TypeIndexLogic['getScopedAppsFromIndex']>>\n\n for (const scope of scopes) {\n const apps = await typeIndexLogic.getScopedAppsFromIndex(scope, ns.vcard('AddressBook'))\n scopedApps = scopedApps.concat(apps)\n }\n\n return uniqueNamedNodes(scopedApps.map(app => app.instance))\n })().catch(error => {\n personalAddressBooksCache.delete(user.value)\n throw error\n })\n\n personalAddressBooksCache.set(user.value, promise)\n return promise\n }\n\n async function webIdForAddressBookContact (contact: NamedNode): Promise<string | null> {\n const cached = contactWebIdCache.get(contact.value)\n if (cached) {\n return cached\n }\n\n const promise = (async () => {\n if (!await loadDocumentSilently(store, contact)) {\n return null\n }\n\n const urlNodes = store.each(contact, ns.vcard('url'), null, contact.doc()) as NamedNode[]\n for (const urlNode of urlNodes) {\n const webId = resolveWebIdFromAddressBookUrlNode(urlNode, contact.doc())\n if (webId) {\n return webId\n }\n }\n\n return null\n })().catch(error => {\n contactWebIdCache.delete(contact.value)\n throw error\n })\n\n contactWebIdCache.set(contact.value, promise)\n return promise\n }\n\n function resolveWebIdFromAddressBookUrlNode (urlNode: NamedNode, doc: NamedNode): string | null {\n if (!store.holds(urlNode, ns.rdf('type'), ns.vcard('WebID'), doc)) {\n return null\n }\n\n const valueNode = store.any(urlNode, ns.vcard('value'), null, doc)\n const webId = valueNode?.value || ''\n return isHttpUri(webId) ? webId : null\n }\n\n async function discoverAddressBookContacts (\n user: NamedNode,\n onEntry: (entry: DirectoryEntry) => void | Promise<void>\n ): Promise<void> {\n const books = await loadPersonalAddressBooks(user)\n\n for (const book of books) {\n if (await loadDocumentSilently(store, book)) {\n const nameEmailIndex = store.any(book, ns.vcard('nameEmailIndex'), null, book.doc()) as NamedNode | null\n if (nameEmailIndex) {\n if (await loadDocumentSilently(store, nameEmailIndex)) {\n const contacts = store.each(undefined, ns.vcard('inAddressBook'), book, nameEmailIndex) as NamedNode[]\n for (let index = 0; index < contacts.length; index += CONTACT_CARD_CONCURRENCY) {\n const batch = contacts.slice(index, index + CONTACT_CARD_CONCURRENCY)\n const entries = await Promise.all(batch.map(async contact => {\n const webId = await webIdForAddressBookContact(contact)\n if (!webId) {\n return null\n }\n\n return {\n kind: 'person' as const,\n uri: webId,\n label: store.anyValue(contact, ns.vcard('fn'), null, nameEmailIndex) || labelForNode(store, contact),\n subjectType: 'agent' as const,\n relationshipLabel: 'Contact' as const,\n sources: ['contacts'] as DirectorySource[]\n }\n }))\n\n for (const entry of entries.filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))) {\n await onEntry(entry)\n }\n }\n }\n }\n }\n }\n }\n\n async function discoverAddressBookGroups (\n user: NamedNode,\n onEntry: (entry: DirectoryEntry) => void | Promise<void>\n ): Promise<void> {\n const books = await loadPersonalAddressBooks(user)\n\n for (const book of books) {\n if (await loadDocumentSilently(store, book)) {\n const groupIndex = store.any(book, ns.vcard('groupIndex'), null, book.doc()) as NamedNode | null\n if (groupIndex) {\n if (await loadDocumentSilently(store, groupIndex)) {\n const groups = uniqueNamedNodes([\n ...(store.each(book, ns.vcard('includesGroup'), null, groupIndex) as NamedNode[]),\n ...(store.each(book, ns.vcard('includesGroup'), null, book.doc()) as NamedNode[])\n ])\n\n for (const group of groups) {\n await loadDocumentSilently(store, group)\n\n await onEntry({\n kind: 'group',\n uri: group.value,\n label: labelForNode(store, group),\n subjectType: 'agentGroup',\n relationshipLabel: 'Group',\n sources: ['groups']\n })\n }\n }\n }\n }\n }\n }\n\n return {\n discoverAddressBookContacts,\n discoverAddressBookGroups\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type DirectoryEntry, type TypeIndexLogic } from '../types';
2
+ import { type LiveStore, NamedNode } from 'rdflib';
3
+ export declare function createContactsDirectoryDiscovery(store: LiveStore, typeIndexLogic: TypeIndexLogic): {
4
+ discoverAddressBookContacts: (user: NamedNode, onEntry: (entry: DirectoryEntry) => void | Promise<void>) => Promise<void>;
5
+ discoverAddressBookGroups: (user: NamedNode, onEntry: (entry: DirectoryEntry) => void | Promise<void>) => Promise<void>;
6
+ };
7
+ //# sourceMappingURL=contactsDirectory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contactsDirectory.d.ts","sourceRoot":"","sources":["../../src/directory/contactsDirectory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAwB,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AACzF,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAMlD,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,cAAc;wCAsEtB,SAAS,WACN,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KACvD,OAAO,CAAC,IAAI,CAAC;sCAsCR,SAAS,WACN,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KACvD,OAAO,CAAC,IAAI,CAAC;EAmCjB"}