@undefineds.co/xpod 0.4.9 → 0.4.10

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 (96) hide show
  1. package/dist/identity/drizzle/PodLookupRepository.d.ts +5 -16
  2. package/dist/identity/drizzle/PodLookupRepository.js +40 -201
  3. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +1 -0
  4. package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +1 -0
  5. package/node_modules/@undefineds.co/ai-connections/dist/controller.js +94 -16
  6. package/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +112 -11
  7. package/node_modules/@undefineds.co/solid-sdk/dist/session.d.ts +2 -0
  8. package/node_modules/@undefineds.co/solid-sdk/dist/session.js +135 -11
  9. package/package.json +2 -2
  10. package/static/app/assets/main.js +12 -12
  11. package/static/auth-callback/assets/{AiConfigContext-DJKvxduw.js → AiConfigContext-C81deMM9.js} +1 -1
  12. package/static/auth-callback/assets/{AiConfigPage-DgGmHb18.js → AiConfigPage-evWI8T56.js} +1 -1
  13. package/static/auth-callback/assets/{DocumentProcessingPanel-CJmxu8x1.js → DocumentProcessingPanel-B9GrDEMe.js} +1 -1
  14. package/static/auth-callback/assets/{IndexLifecyclePanel-byCa7h7L.js → IndexLifecyclePanel-qCo8ABQZ.js} +1 -1
  15. package/static/auth-callback/assets/{IndexSubjectPanel-CDwT4Yx1.js → IndexSubjectPanel-C4YG0whI.js} +1 -1
  16. package/static/auth-callback/assets/{ModelAssignmentsPanel-CwM3k8w7.js → ModelAssignmentsPanel-DG7Lxh3t.js} +1 -1
  17. package/static/auth-callback/assets/ModelsPage-CgEuqE6p.js +10 -0
  18. package/static/auth-callback/assets/{NetworkPage-DkVgxtNj.js → NetworkPage-Cbdn_P1e.js} +1 -1
  19. package/static/auth-callback/assets/{PaneListHeader-DRI0hjmt.js → PaneListHeader-jy70Q00L.js} +1 -1
  20. package/static/auth-callback/assets/{SearchIndexingPanel-mbppDZFE.js → SearchIndexingPanel-ByU3u7kp.js} +1 -1
  21. package/static/auth-callback/assets/{StatusSubjectPanel-DNsW76aC.js → StatusSubjectPanel-hfjoB5aS.js} +1 -1
  22. package/static/auth-callback/assets/{StatusWorkspace-DGcTbHT5.js → StatusWorkspace-BZd617sV.js} +1 -1
  23. package/static/auth-callback/assets/{SystemSettingsPage-C6qklJ9k.js → SystemSettingsPage-BirzWAQM.js} +1 -1
  24. package/static/auth-callback/assets/{SystemSettingsSubjectPanel-B4VV9xkG.js → SystemSettingsSubjectPanel-CmsVWNCx.js} +1 -1
  25. package/static/auth-callback/assets/{activity-Ck-Mfxxo.js → activity-BcsZZxxw.js} +1 -1
  26. package/static/auth-callback/assets/{auth-callback-DG4Ge40c.js → auth-callback-atKg_oig.js} +124 -124
  27. package/static/auth-callback/assets/{badge-GSSnw1VV.js → badge-BWbRHThQ.js} +1 -1
  28. package/static/auth-callback/assets/{download-BYyRX-K2.js → download-BmvG1Vya.js} +1 -1
  29. package/static/auth-callback/assets/{external-link-WEC0Ki-0.js → external-link-DZ34d3g3.js} +1 -1
  30. package/static/auth-callback/assets/{index-kju2k4Dp.js → index-BpV1hsqs.js} +1 -1
  31. package/static/auth-callback/assets/{index-DlsMCgo8.js → index-D9oE6FXK.js} +1 -1
  32. package/static/auth-callback/assets/{index-browser-D8YW6coF.js → index-browser-B9Z7GU-T.js} +1 -1
  33. package/static/auth-callback/assets/{key-round-DJUT5Nh0.js → key-round-tjP76Se-.js} +1 -1
  34. package/static/auth-callback/assets/{rotate-ccw-CXwfZUKU.js → rotate-ccw-C_L5ZLx1.js} +1 -1
  35. package/static/auth-callback/assets/{skeleton-DWPtNhUB.js → skeleton-C7tGDQIs.js} +1 -1
  36. package/static/auth-callback/assets/{waypoints-l1oDnRJ0.js → waypoints-VMNQlWdU.js} +1 -1
  37. package/static/auth-callback/assets/{workspace-layout-B7iKpK-Q.js → workspace-layout-SAp6BtYK.js} +1 -1
  38. package/static/auth-callback/auth-callback.html +1 -1
  39. package/static/dashboard/assets/{AiConfigContext-U0cCSY28.js → AiConfigContext-De-OtAWo.js} +1 -1
  40. package/static/dashboard/assets/{AiConfigPage-CMi0uJdo.js → AiConfigPage-macP4Mop.js} +1 -1
  41. package/static/dashboard/assets/{DocumentProcessingPanel-DEZiEpft.js → DocumentProcessingPanel-C5ObvCCl.js} +1 -1
  42. package/static/dashboard/assets/{IndexLifecyclePanel-CGZfLUog.js → IndexLifecyclePanel-xVNa2l40.js} +1 -1
  43. package/static/dashboard/assets/{IndexSubjectPanel-DiDZauHk.js → IndexSubjectPanel-DQFPP6-p.js} +1 -1
  44. package/static/dashboard/assets/{ModelAssignmentsPanel-DaGGZKuf.js → ModelAssignmentsPanel-DMOATFpQ.js} +1 -1
  45. package/static/dashboard/assets/ModelsPage-91UbnETA.js +25 -0
  46. package/static/dashboard/assets/{NetworkPage-DI5-O7GT.js → NetworkPage-CHQ3YxIB.js} +1 -1
  47. package/static/dashboard/assets/{PaneListHeader-4oa7uf59.js → PaneListHeader-DsXemAdR.js} +1 -1
  48. package/static/dashboard/assets/{SearchIndexingPanel-DoqEwocD.js → SearchIndexingPanel-Dh70PNFZ.js} +1 -1
  49. package/static/dashboard/assets/{StatusSubjectPanel-CLzBC5fu.js → StatusSubjectPanel-D3PGEYT4.js} +1 -1
  50. package/static/dashboard/assets/{StatusWorkspace-UyKADZoM.js → StatusWorkspace-BcpePYUW.js} +1 -1
  51. package/static/dashboard/assets/{SystemSettingsPage-qP0hLHNV.js → SystemSettingsPage-BiN5GcKx.js} +1 -1
  52. package/static/dashboard/assets/{SystemSettingsSubjectPanel-EaWax9Ot.js → SystemSettingsSubjectPanel-B9QrSiFp.js} +1 -1
  53. package/static/dashboard/assets/{activity-CjIoNU4S.js → activity-j5zrznu-.js} +1 -1
  54. package/static/dashboard/assets/{badge-dcDZpKh3.js → badge-CzeBdG7h.js} +1 -1
  55. package/static/dashboard/assets/{dashboard-BY8GdnEI.js → dashboard-D-xANHk1.js} +103 -103
  56. package/static/dashboard/assets/{download-BeYCrc-_.js → download-IcNdEZWi.js} +1 -1
  57. package/static/dashboard/assets/{external-link-626-5KVx.js → external-link-DxBxK8Vu.js} +1 -1
  58. package/static/dashboard/assets/{index-CBbk6hx-.js → index-FWZ-s52O.js} +1 -1
  59. package/static/dashboard/assets/{index-CsxH6Hmz.js → index-ZiQZ96oC.js} +1 -1
  60. package/static/dashboard/assets/{key-round-B9IL4Wua.js → key-round-D1K7warM.js} +1 -1
  61. package/static/dashboard/assets/{rotate-ccw-DM4vCfiI.js → rotate-ccw-CcN1N6p9.js} +1 -1
  62. package/static/dashboard/assets/{skeleton-ChlFAPvP.js → skeleton-CKnmJMUH.js} +1 -1
  63. package/static/dashboard/assets/{waypoints-CvpZzf_I.js → waypoints-DuZPO_wu.js} +1 -1
  64. package/static/dashboard/assets/{workspace-layout-Bxv7V-tt.js → workspace-layout-BUj3wZ_m.js} +1 -1
  65. package/static/dashboard/dashboard.html +1 -1
  66. package/static/settings/assets/{AiConfigContext-CDVIHLBb.js → AiConfigContext-C0cQMPfG.js} +1 -1
  67. package/static/settings/assets/{AiConfigPage-C7qfiiaJ.js → AiConfigPage-CPhB4iFb.js} +1 -1
  68. package/static/settings/assets/{DocumentProcessingPanel-B59QTe-V.js → DocumentProcessingPanel-sYI7OpuH.js} +1 -1
  69. package/static/settings/assets/{IndexLifecyclePanel-C-e-3eVi.js → IndexLifecyclePanel-yj8iyiT8.js} +1 -1
  70. package/static/settings/assets/{IndexSubjectPanel-B-scRZ1u.js → IndexSubjectPanel-73sM16Ma.js} +1 -1
  71. package/static/settings/assets/{ModelAssignmentsPanel-Wy8jzVg4.js → ModelAssignmentsPanel-BFIgUX5v.js} +1 -1
  72. package/static/settings/assets/ModelsPage-BdppXl7E.js +10 -0
  73. package/static/settings/assets/{NetworkPage-tBB-4Ox8.js → NetworkPage-i6j-M2RY.js} +1 -1
  74. package/static/settings/assets/{PaneListHeader-DuD6glzn.js → PaneListHeader-DpqC0jd5.js} +1 -1
  75. package/static/settings/assets/{SearchIndexingPanel-CiDHc0nx.js → SearchIndexingPanel-BlPnrkT3.js} +1 -1
  76. package/static/settings/assets/{StatusSubjectPanel-OF5cgWwt.js → StatusSubjectPanel-Dv4q3PGh.js} +1 -1
  77. package/static/settings/assets/{StatusWorkspace-CZ8Oa0gV.js → StatusWorkspace-CKD_Rzdr.js} +1 -1
  78. package/static/settings/assets/{SystemSettingsPage-DT2tC0F_.js → SystemSettingsPage-CiOm2NzV.js} +1 -1
  79. package/static/settings/assets/{SystemSettingsSubjectPanel-Bkta1oDw.js → SystemSettingsSubjectPanel-Cd-GECon.js} +1 -1
  80. package/static/settings/assets/{activity-2M1f5eBS.js → activity-DaFkk8f3.js} +1 -1
  81. package/static/settings/assets/{badge-CRKan0qX.js → badge-d5SHdc-x.js} +1 -1
  82. package/static/settings/assets/{download-PCHldCdu.js → download-CpkwomDr.js} +1 -1
  83. package/static/settings/assets/{external-link-tmKe7eYO.js → external-link-_4YT42Mf.js} +1 -1
  84. package/static/settings/assets/{index-Cek02M79.js → index-BFi9yn7_.js} +1 -1
  85. package/static/settings/assets/{index-BDCNTwWz.js → index-BGgEhIUy.js} +1 -1
  86. package/static/settings/assets/{index-browser-EVDVm9T5.js → index-browser-B06xjBdZ.js} +1 -1
  87. package/static/settings/assets/{key-round-CUGvQCkt.js → key-round-Dlqmp1Kq.js} +1 -1
  88. package/static/settings/assets/{rotate-ccw-BRA3lIHM.js → rotate-ccw-TAY65xWT.js} +1 -1
  89. package/static/settings/assets/{settings-BdlKrzJ3.js → settings-ExBXGgbj.js} +103 -103
  90. package/static/settings/assets/{skeleton-CuDeifWb.js → skeleton-DKIWjNr9.js} +1 -1
  91. package/static/settings/assets/{waypoints-w6LxsFY1.js → waypoints-9erLIxyR.js} +1 -1
  92. package/static/settings/assets/{workspace-layout-TN-BWmT5.js → workspace-layout-CYXfYIa3.js} +1 -1
  93. package/static/settings/settings.html +1 -1
  94. package/static/auth-callback/assets/ModelsPage-DapY2Dwf.js +0 -10
  95. package/static/dashboard/assets/ModelsPage-DZgAOdxO.js +0 -25
  96. package/static/settings/assets/ModelsPage-DHNiaodk.js +0 -10
@@ -117,11 +117,24 @@ function createSolidSessionRuntime(options = {}) {
117
117
  let isInitializing = false;
118
118
  let initializationErrorSnapshot;
119
119
  let disposed = false;
120
+ let authenticationGeneration = 0;
121
+ let authenticationUnavailable = false;
122
+ let authenticationOperations = 0;
123
+ let identityOperationGeneration = 0;
124
+ let pendingIdentity;
125
+ const invalidateAuthentication = () => {
126
+ authenticationGeneration += 1;
127
+ authenticationUnavailable = true;
128
+ };
120
129
  const publish = (nextSnapshot) => {
121
- snapshot = nextSnapshot;
122
- if (disposed) {
130
+ if (disposed)
123
131
  return snapshot;
132
+ if (nextSnapshot.status !== "authenticated" || snapshot.status !== "authenticated" || snapshot.webId !== nextSnapshot.webId) {
133
+ invalidateAuthentication();
124
134
  }
135
+ if (nextSnapshot.status === "authenticated")
136
+ authenticationUnavailable = false;
137
+ snapshot = nextSnapshot;
125
138
  if (areSnapshotsEqual(lastNotifiedSnapshot, snapshot)) {
126
139
  return snapshot;
127
140
  }
@@ -131,10 +144,28 @@ function createSolidSessionRuntime(options = {}) {
131
144
  }
132
145
  return snapshot;
133
146
  };
134
- const publishSessionInfo = () => publish(snapshotFromSessionInfo(session.info));
135
- const publishAnonymous = () => publish({ status: "anonymous" });
136
- const publishSessionExpired = () => publish(snapshotFromSessionExpired(session.info));
147
+ const publishSessionInfo = () => {
148
+ if (pendingIdentity) {
149
+ if (pendingIdentity.generation !== identityOperationGeneration)
150
+ return snapshot;
151
+ if (pendingIdentity.eventWebId && pendingIdentity.eventWebId !== session.info.webId)
152
+ return snapshot;
153
+ pendingIdentity.eventWebId = session.info.webId;
154
+ }
155
+ invalidateAuthentication();
156
+ lastNotifiedSnapshot = undefined;
157
+ return publish(snapshotFromSessionInfo(session.info));
158
+ };
159
+ const publishAnonymous = () => {
160
+ identityOperationGeneration += 1;
161
+ return publish({ status: "anonymous" });
162
+ };
163
+ const publishSessionExpired = () => {
164
+ identityOperationGeneration += 1;
165
+ return publish(snapshotFromSessionExpired(session.info));
166
+ };
137
167
  const publishSessionError = (code, description) => {
168
+ identityOperationGeneration += 1;
138
169
  const errorSnapshot = snapshotFromSessionError(description ?? code ?? "Solid session error", session.info);
139
170
  if (isInitializing) {
140
171
  initializationErrorSnapshot = errorSnapshot;
@@ -149,6 +180,29 @@ function createSolidSessionRuntime(options = {}) {
149
180
  errorEvents.on(import_solid_client_authn_browser.EVENTS.ERROR, publishSessionError);
150
181
  return {
151
182
  fetch: session.fetch,
183
+ createAuthenticatedFetch(webId) {
184
+ const generation = authenticationGeneration;
185
+ const assertCurrent = () => {
186
+ if (disposed || authenticationOperations > 0 || authenticationUnavailable || generation !== authenticationGeneration || snapshot.status !== "authenticated" || snapshot.webId !== webId || !session.info.isLoggedIn || session.info.webId !== webId) {
187
+ const error = new Error("Authenticated session is no longer current");
188
+ error.name = "AbortError";
189
+ throw error;
190
+ }
191
+ };
192
+ return async (input, init) => {
193
+ assertCurrent();
194
+ const response = await (init === undefined ? session.fetch(input) : session.fetch(input, init));
195
+ try {
196
+ assertCurrent();
197
+ } catch (error) {
198
+ response.body?.cancel().catch(() => {
199
+ return;
200
+ });
201
+ throw error;
202
+ }
203
+ return response;
204
+ };
205
+ },
152
206
  getSnapshot() {
153
207
  return snapshot;
154
208
  },
@@ -159,20 +213,29 @@ function createSolidSessionRuntime(options = {}) {
159
213
  if (initialized) {
160
214
  return Promise.resolve(snapshot);
161
215
  }
216
+ if (disposed)
217
+ return Promise.resolve(snapshot);
218
+ const operation = { generation: ++identityOperationGeneration, eventWebId: undefined };
219
+ pendingIdentity = operation;
220
+ const isCurrentOperation = (info) => !disposed && operation.generation === identityOperationGeneration && (!info?.isLoggedIn || !operation.eventWebId || operation.eventWebId === info.webId);
162
221
  isInitializing = true;
163
222
  initializationErrorSnapshot = undefined;
164
223
  publish({ status: "initializing" });
165
224
  const nextInitialization = session.handleIncomingRedirect({
166
225
  restorePreviousSession: options2.restorePreviousSession ?? true
167
226
  }).then((info) => {
227
+ if (!isCurrentOperation(info ?? session.info))
228
+ return snapshot;
168
229
  const nextSnapshot = snapshotFromSessionInfo(info ?? session.info);
169
230
  if (nextSnapshot.status === "anonymous" && initializationErrorSnapshot?.status === "error") {
170
231
  return initializationErrorSnapshot;
171
232
  }
172
233
  initialized = true;
173
234
  return publish(nextSnapshot);
174
- }).catch((error) => publish(snapshotFromSessionError(error, session.info))).finally(() => {
235
+ }).catch((error) => isCurrentOperation() ? publish(snapshotFromSessionError(error, session.info)) : snapshot).finally(() => {
175
236
  if (initialization === nextInitialization) {
237
+ if (pendingIdentity === operation)
238
+ pendingIdentity = undefined;
176
239
  initialization = undefined;
177
240
  isInitializing = false;
178
241
  initializationErrorSnapshot = undefined;
@@ -185,18 +248,27 @@ function createSolidSessionRuntime(options = {}) {
185
248
  if (initialization) {
186
249
  return initialization;
187
250
  }
251
+ if (disposed)
252
+ return Promise.resolve(snapshot);
253
+ const operation = { generation: ++identityOperationGeneration, eventWebId: undefined };
254
+ pendingIdentity = operation;
255
+ const isCurrentOperation = (info) => !disposed && operation.generation === identityOperationGeneration && (!info?.isLoggedIn || !operation.eventWebId || operation.eventWebId === info.webId);
188
256
  isInitializing = true;
189
257
  initializationErrorSnapshot = undefined;
190
258
  publish({ status: "initializing" });
191
259
  const nextInitialization = session.handleIncomingRedirect(url).then((info) => {
260
+ if (!isCurrentOperation(info ?? session.info))
261
+ return snapshot;
192
262
  const nextSnapshot = snapshotFromSessionInfo(info ?? session.info);
193
263
  if (nextSnapshot.status === "anonymous" && initializationErrorSnapshot?.status === "error") {
194
264
  return initializationErrorSnapshot;
195
265
  }
196
266
  initialized = true;
197
267
  return publish(nextSnapshot);
198
- }).catch((error) => publish(snapshotFromSessionError(error, session.info))).finally(() => {
268
+ }).catch((error) => isCurrentOperation() ? publish(snapshotFromSessionError(error, session.info)) : snapshot).finally(() => {
199
269
  if (initialization === nextInitialization) {
270
+ if (pendingIdentity === operation)
271
+ pendingIdentity = undefined;
200
272
  initialization = undefined;
201
273
  isInitializing = false;
202
274
  initializationErrorSnapshot = undefined;
@@ -205,12 +277,39 @@ function createSolidSessionRuntime(options = {}) {
205
277
  initialization = nextInitialization;
206
278
  return nextInitialization;
207
279
  },
208
- login(options2) {
209
- return session.login(options2);
280
+ async login(options2) {
281
+ identityOperationGeneration += 1;
282
+ invalidateAuthentication();
283
+ authenticationOperations += 1;
284
+ try {
285
+ if (initialization) {
286
+ const error = new Error("Previous login is still completing. Reload before reconnecting.");
287
+ error.name = "SolidSessionPendingError";
288
+ throw error;
289
+ }
290
+ if (disposed)
291
+ throw new Error("Solid session runtime is disposed");
292
+ await session.login(options2);
293
+ } catch (error) {
294
+ publish(snapshotFromSessionError(error, session.info));
295
+ throw error;
296
+ } finally {
297
+ authenticationOperations -= 1;
298
+ }
210
299
  },
211
300
  async logout(options2) {
212
- await session.logout(options2);
213
- publish({ status: "anonymous" });
301
+ identityOperationGeneration += 1;
302
+ invalidateAuthentication();
303
+ authenticationOperations += 1;
304
+ try {
305
+ await session.logout(options2);
306
+ publish({ status: "anonymous" });
307
+ } catch (error) {
308
+ publish(snapshotFromSessionError(error, session.info));
309
+ throw error;
310
+ } finally {
311
+ authenticationOperations -= 1;
312
+ }
214
313
  },
215
314
  subscribe(listener) {
216
315
  listeners.add(listener);
@@ -219,6 +318,8 @@ function createSolidSessionRuntime(options = {}) {
219
318
  };
220
319
  },
221
320
  dispose() {
321
+ identityOperationGeneration += 1;
322
+ invalidateAuthentication();
222
323
  disposed = true;
223
324
  listeners.clear();
224
325
  session.events.off(import_solid_client_authn_browser.EVENTS.LOGIN, publishSessionInfo);
@@ -31,6 +31,8 @@ export type SolidSessionAdapter = {
31
31
  };
32
32
  export type SolidSessionRuntime = {
33
33
  readonly fetch: typeof fetch;
34
+ /** A revocable capability for this exact WebID and authenticated session. */
35
+ createAuthenticatedFetch(webId: string): typeof fetch;
34
36
  getSnapshot(): SolidSessionSnapshot;
35
37
  initialize(options?: {
36
38
  restorePreviousSession?: boolean;
@@ -46,11 +46,27 @@ export function createSolidSessionRuntime(options = {}) {
46
46
  let isInitializing = false;
47
47
  let initializationErrorSnapshot;
48
48
  let disposed = false;
49
+ let authenticationGeneration = 0;
50
+ let authenticationUnavailable = false;
51
+ let authenticationOperations = 0;
52
+ // Redirect completion has a lifecycle separate from signer renewal. Its own
53
+ // LOGIN event may advance authenticationGeneration without cancelling it.
54
+ let identityOperationGeneration = 0;
55
+ let pendingIdentity;
56
+ const invalidateAuthentication = () => {
57
+ authenticationGeneration += 1;
58
+ authenticationUnavailable = true;
59
+ };
49
60
  const publish = (nextSnapshot) => {
50
- snapshot = nextSnapshot;
51
- if (disposed) {
61
+ if (disposed)
52
62
  return snapshot;
63
+ if (nextSnapshot.status !== 'authenticated'
64
+ || snapshot.status !== 'authenticated' || snapshot.webId !== nextSnapshot.webId) {
65
+ invalidateAuthentication();
53
66
  }
67
+ if (nextSnapshot.status === 'authenticated')
68
+ authenticationUnavailable = false;
69
+ snapshot = nextSnapshot;
54
70
  if (areSnapshotsEqual(lastNotifiedSnapshot, snapshot)) {
55
71
  return snapshot;
56
72
  }
@@ -60,10 +76,32 @@ export function createSolidSessionRuntime(options = {}) {
60
76
  }
61
77
  return snapshot;
62
78
  };
63
- const publishSessionInfo = () => publish(snapshotFromSessionInfo(session.info));
64
- const publishAnonymous = () => publish({ status: 'anonymous' });
65
- const publishSessionExpired = () => publish(snapshotFromSessionExpired(session.info));
79
+ const publishSessionInfo = () => {
80
+ // Inrupt may emit LOGIN while a cancelled redirect is still settling.
81
+ // These events cannot restore authority after logout/disposal.
82
+ if (pendingIdentity) {
83
+ if (pendingIdentity.generation !== identityOperationGeneration)
84
+ return snapshot;
85
+ if (pendingIdentity.eventWebId && pendingIdentity.eventWebId !== session.info.webId)
86
+ return snapshot;
87
+ pendingIdentity.eventWebId = session.info.webId;
88
+ }
89
+ // LOGIN/RESTORED establishes a new session even when the WebID is unchanged.
90
+ // Token renewal changes Inrupt's signer without these login events.
91
+ invalidateAuthentication();
92
+ lastNotifiedSnapshot = undefined;
93
+ return publish(snapshotFromSessionInfo(session.info));
94
+ };
95
+ const publishAnonymous = () => {
96
+ identityOperationGeneration += 1;
97
+ return publish({ status: 'anonymous' });
98
+ };
99
+ const publishSessionExpired = () => {
100
+ identityOperationGeneration += 1;
101
+ return publish(snapshotFromSessionExpired(session.info));
102
+ };
66
103
  const publishSessionError = (code, description) => {
104
+ identityOperationGeneration += 1;
67
105
  const errorSnapshot = snapshotFromSessionError(description ?? code ?? 'Solid session error', session.info);
68
106
  if (isInitializing) {
69
107
  initializationErrorSnapshot = errorSnapshot;
@@ -78,6 +116,32 @@ export function createSolidSessionRuntime(options = {}) {
78
116
  errorEvents.on(EVENTS.ERROR, publishSessionError);
79
117
  return {
80
118
  fetch: session.fetch,
119
+ createAuthenticatedFetch(webId) {
120
+ const generation = authenticationGeneration;
121
+ const assertCurrent = () => {
122
+ if (disposed || authenticationOperations > 0 || authenticationUnavailable || generation !== authenticationGeneration
123
+ || snapshot.status !== 'authenticated' || snapshot.webId !== webId
124
+ || !session.info.isLoggedIn || session.info.webId !== webId) {
125
+ const error = new Error('Authenticated session is no longer current');
126
+ error.name = 'AbortError';
127
+ throw error;
128
+ }
129
+ };
130
+ return async (input, init) => {
131
+ assertCurrent();
132
+ const response = await (init === undefined ? session.fetch(input) : session.fetch(input, init));
133
+ try {
134
+ assertCurrent();
135
+ }
136
+ catch (error) {
137
+ // The request may already have reached the server. Discard its stale
138
+ // response rather than returning it to the previous identity's work.
139
+ void response.body?.cancel().catch(() => undefined);
140
+ throw error;
141
+ }
142
+ return response;
143
+ };
144
+ },
81
145
  getSnapshot() {
82
146
  return snapshot;
83
147
  },
@@ -92,12 +156,21 @@ export function createSolidSessionRuntime(options = {}) {
92
156
  if (initialized) {
93
157
  return Promise.resolve(snapshot);
94
158
  }
159
+ if (disposed)
160
+ return Promise.resolve(snapshot);
161
+ const operation = { generation: ++identityOperationGeneration, eventWebId: undefined };
162
+ pendingIdentity = operation;
163
+ const isCurrentOperation = (info) => !disposed
164
+ && operation.generation === identityOperationGeneration
165
+ && (!info?.isLoggedIn || !operation.eventWebId || operation.eventWebId === info.webId);
95
166
  isInitializing = true;
96
167
  initializationErrorSnapshot = undefined;
97
168
  publish({ status: 'initializing' });
98
169
  const nextInitialization = session.handleIncomingRedirect({
99
170
  restorePreviousSession: options.restorePreviousSession ?? true,
100
171
  }).then((info) => {
172
+ if (!isCurrentOperation(info ?? session.info))
173
+ return snapshot;
101
174
  const nextSnapshot = snapshotFromSessionInfo(info ?? session.info);
102
175
  if (nextSnapshot.status === 'anonymous' && initializationErrorSnapshot?.status === 'error') {
103
176
  return initializationErrorSnapshot;
@@ -105,9 +178,12 @@ export function createSolidSessionRuntime(options = {}) {
105
178
  initialized = true;
106
179
  return publish(nextSnapshot);
107
180
  })
108
- .catch((error) => publish(snapshotFromSessionError(error, session.info)))
181
+ .catch((error) => isCurrentOperation()
182
+ ? publish(snapshotFromSessionError(error, session.info)) : snapshot)
109
183
  .finally(() => {
110
184
  if (initialization === nextInitialization) {
185
+ if (pendingIdentity === operation)
186
+ pendingIdentity = undefined;
111
187
  initialization = undefined;
112
188
  isInitializing = false;
113
189
  initializationErrorSnapshot = undefined;
@@ -120,10 +196,19 @@ export function createSolidSessionRuntime(options = {}) {
120
196
  if (initialization) {
121
197
  return initialization;
122
198
  }
199
+ if (disposed)
200
+ return Promise.resolve(snapshot);
201
+ const operation = { generation: ++identityOperationGeneration, eventWebId: undefined };
202
+ pendingIdentity = operation;
203
+ const isCurrentOperation = (info) => !disposed
204
+ && operation.generation === identityOperationGeneration
205
+ && (!info?.isLoggedIn || !operation.eventWebId || operation.eventWebId === info.webId);
123
206
  isInitializing = true;
124
207
  initializationErrorSnapshot = undefined;
125
208
  publish({ status: 'initializing' });
126
209
  const nextInitialization = session.handleIncomingRedirect(url).then((info) => {
210
+ if (!isCurrentOperation(info ?? session.info))
211
+ return snapshot;
127
212
  const nextSnapshot = snapshotFromSessionInfo(info ?? session.info);
128
213
  if (nextSnapshot.status === 'anonymous' && initializationErrorSnapshot?.status === 'error') {
129
214
  return initializationErrorSnapshot;
@@ -131,9 +216,12 @@ export function createSolidSessionRuntime(options = {}) {
131
216
  initialized = true;
132
217
  return publish(nextSnapshot);
133
218
  })
134
- .catch((error) => publish(snapshotFromSessionError(error, session.info)))
219
+ .catch((error) => isCurrentOperation()
220
+ ? publish(snapshotFromSessionError(error, session.info)) : snapshot)
135
221
  .finally(() => {
136
222
  if (initialization === nextInitialization) {
223
+ if (pendingIdentity === operation)
224
+ pendingIdentity = undefined;
137
225
  initialization = undefined;
138
226
  isInitializing = false;
139
227
  initializationErrorSnapshot = undefined;
@@ -142,12 +230,46 @@ export function createSolidSessionRuntime(options = {}) {
142
230
  initialization = nextInitialization;
143
231
  return nextInitialization;
144
232
  },
145
- login(options) {
146
- return session.login(options);
233
+ async login(options) {
234
+ identityOperationGeneration += 1;
235
+ invalidateAuthentication();
236
+ authenticationOperations += 1;
237
+ try {
238
+ // Inrupt mutates one shared signer and emits untagged events. A new
239
+ // login cannot safely overlap a previous redirect. Fail promptly so
240
+ // callers can retry after it settles, or reload to obtain a new Session.
241
+ if (initialization) {
242
+ const error = new Error('Previous login is still completing. Reload before reconnecting.');
243
+ error.name = 'SolidSessionPendingError';
244
+ throw error;
245
+ }
246
+ if (disposed)
247
+ throw new Error('Solid session runtime is disposed');
248
+ await session.login(options);
249
+ }
250
+ catch (error) {
251
+ publish(snapshotFromSessionError(error, session.info));
252
+ throw error;
253
+ }
254
+ finally {
255
+ authenticationOperations -= 1;
256
+ }
147
257
  },
148
258
  async logout(options) {
149
- await session.logout(options);
150
- publish({ status: 'anonymous' });
259
+ identityOperationGeneration += 1;
260
+ invalidateAuthentication();
261
+ authenticationOperations += 1;
262
+ try {
263
+ await session.logout(options);
264
+ publish({ status: 'anonymous' });
265
+ }
266
+ catch (error) {
267
+ publish(snapshotFromSessionError(error, session.info));
268
+ throw error;
269
+ }
270
+ finally {
271
+ authenticationOperations -= 1;
272
+ }
151
273
  },
152
274
  subscribe(listener) {
153
275
  listeners.add(listener);
@@ -156,6 +278,8 @@ export function createSolidSessionRuntime(options = {}) {
156
278
  };
157
279
  },
158
280
  dispose() {
281
+ identityOperationGeneration += 1;
282
+ invalidateAuthentication();
159
283
  disposed = true;
160
284
  listeners.clear();
161
285
  session.events.off(EVENTS.LOGIN, publishSessionInfo);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.4.9",
3
+ "version": "0.4.10",
4
4
  "description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
5
5
  "repository": "https://github.com/undefinedsco/xpod",
6
6
  "author": "developer@undefineds.co",
@@ -376,7 +376,7 @@
376
376
  }
377
377
  },
378
378
  "optionalDependencies": {
379
- "@undefineds.co/xpod-darwin-arm64": "0.4.9"
379
+ "@undefineds.co/xpod-darwin-arm64": "0.4.10"
380
380
  },
381
381
  "resolutions": {
382
382
  "@inrupt/solid-client-authn-browser": "^3.1.1",