@opengeni/react 2.5.0-canary.1 → 3.0.0-canary.0

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 (70) hide show
  1. package/README.md +15 -0
  2. package/dist/accounts.d.ts +64 -0
  3. package/dist/accounts.js +746 -0
  4. package/dist/accounts.js.map +1 -0
  5. package/dist/{browser-viewer-GXUICIAG.js → browser-viewer-AEFHUYZK.js} +3 -3
  6. package/dist/{chunk-MXJGO7LE.js → chunk-D3UIAJKY.js} +52 -2
  7. package/dist/chunk-D3UIAJKY.js.map +1 -0
  8. package/dist/{chunk-QGBJ6GHG.js → chunk-ELIYWBZR.js} +4 -4
  9. package/dist/{chunk-6H6S5V4Z.js → chunk-HQVCKCLG.js} +758 -871
  10. package/dist/chunk-HQVCKCLG.js.map +1 -0
  11. package/dist/{chunk-T7VAYG2I.js → chunk-MZ2G2XBA.js} +54 -2
  12. package/dist/chunk-MZ2G2XBA.js.map +1 -0
  13. package/dist/{chunk-X4JAFD5R.js → chunk-OAM4WAAM.js} +4 -4
  14. package/dist/{chunk-KSDXER4X.js → chunk-RGALCTWO.js} +64 -15
  15. package/dist/chunk-RGALCTWO.js.map +1 -0
  16. package/dist/chunk-YFKJ7VZH.js +268 -0
  17. package/dist/chunk-YFKJ7VZH.js.map +1 -0
  18. package/dist/{chunk-ATQJEWCA.js → chunk-YJFGAFBD.js} +124 -50
  19. package/dist/chunk-YJFGAFBD.js.map +1 -0
  20. package/dist/components/composer.d.ts +8 -0
  21. package/dist/components/file-browser.d.ts +4 -1
  22. package/dist/components/sandbox-files.d.ts +4 -1
  23. package/dist/components/sandbox-workspace.d.ts +5 -0
  24. package/dist/composer.js +3 -3
  25. package/dist/{computer-viewer-ZOP4Q7A7.js → computer-viewer-FWEZISW4.js} +3 -3
  26. package/dist/embedded-session-client.d.ts +23 -0
  27. package/dist/file-node-visibility.d.ts +15 -0
  28. package/dist/hooks/use-file-attachments.d.ts +9 -2
  29. package/dist/human-input.d.ts +3 -1
  30. package/dist/index.d.ts +5 -1
  31. package/dist/index.js +139 -53
  32. package/dist/index.js.map +1 -1
  33. package/dist/interaction.js +3 -3
  34. package/dist/session-ui.js +4 -4
  35. package/dist/session.d.ts +2 -0
  36. package/dist/session.js +7 -5
  37. package/dist/timeline/index.d.ts +1 -0
  38. package/dist/timeline/screenshot-lightbox.d.ts +16 -1
  39. package/package.json +6 -2
  40. package/src/accounts.tsx +970 -0
  41. package/src/components/chat-composer.tsx +88 -84
  42. package/src/components/composer.tsx +61 -9
  43. package/src/components/file-browser.tsx +33 -20
  44. package/src/components/human-input-surface.tsx +10 -6
  45. package/src/components/message-timeline.tsx +37 -9
  46. package/src/components/sandbox-files.tsx +42 -2
  47. package/src/components/sandbox-workspace.tsx +10 -0
  48. package/src/embedded-session-client.ts +104 -0
  49. package/src/file-node-visibility.ts +66 -0
  50. package/src/hooks/use-composer.ts +64 -2
  51. package/src/hooks/use-file-attachments.ts +38 -6
  52. package/src/hooks/use-human-input.ts +31 -4
  53. package/src/human-input.ts +12 -1
  54. package/src/index.ts +12 -0
  55. package/src/session.ts +5 -0
  56. package/src/timeline/index.ts +1 -0
  57. package/src/timeline/screenshot-lightbox.tsx +93 -29
  58. package/styles/compiled.css +11 -1
  59. package/styles/tokens.css +3 -1
  60. package/dist/chunk-6H6S5V4Z.js.map +0 -1
  61. package/dist/chunk-ATQJEWCA.js.map +0 -1
  62. package/dist/chunk-FK6VG4LL.js +0 -90
  63. package/dist/chunk-FK6VG4LL.js.map +0 -1
  64. package/dist/chunk-KSDXER4X.js.map +0 -1
  65. package/dist/chunk-MXJGO7LE.js.map +0 -1
  66. package/dist/chunk-T7VAYG2I.js.map +0 -1
  67. /package/dist/{browser-viewer-GXUICIAG.js.map → browser-viewer-AEFHUYZK.js.map} +0 -0
  68. /package/dist/{chunk-QGBJ6GHG.js.map → chunk-ELIYWBZR.js.map} +0 -0
  69. /package/dist/{chunk-X4JAFD5R.js.map → chunk-OAM4WAAM.js.map} +0 -0
  70. /package/dist/{computer-viewer-ZOP4Q7A7.js.map → computer-viewer-FWEZISW4.js.map} +0 -0
@@ -0,0 +1,746 @@
1
+ // src/accounts.tsx
2
+ import {
3
+ createContext,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+ import { jsx } from "react/jsx-runtime";
12
+ var BrowserAccountsContext = createContext(null);
13
+ var DEFAULT_CHANNEL = "opengeni:browser-account-epoch:v1";
14
+ var CROSS_TAB_ACTOR_HOLD_MS = 3e4;
15
+ async function yieldToCrossTabActorHold() {
16
+ await new Promise((resolve) => setTimeout(resolve, 0));
17
+ }
18
+ function operationId() {
19
+ return crypto.randomUUID();
20
+ }
21
+ function sameActor(left, right) {
22
+ return left?.generation === right?.generation && left?.actorEpoch === right?.actorEpoch && left?.selectedSlotId === right?.selectedSlotId && left?.state === right?.state;
23
+ }
24
+ function sameSelectedActor(left, right) {
25
+ return left !== null && right !== null && left.actorEpoch === right.actorEpoch && left.selectedSlotId === right.selectedSlotId && left.state === right.state;
26
+ }
27
+ function compareCounter(left, right) {
28
+ const leftValue = BigInt(left);
29
+ const rightValue = BigInt(right);
30
+ return leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0;
31
+ }
32
+ function isOlderProjection(candidate, current) {
33
+ const epoch = compareCounter(candidate.actorEpoch, current.actorEpoch);
34
+ return epoch < 0 || epoch === 0 && compareCounter(candidate.generation, current.generation) < 0;
35
+ }
36
+ function accountError(error) {
37
+ return error instanceof Error ? error : new Error("Browser account operation failed");
38
+ }
39
+ function supersededOperationError() {
40
+ const error = new Error("Browser account operation was superseded by an actor change");
41
+ error.name = "AbortError";
42
+ return error;
43
+ }
44
+ function errorCode(error) {
45
+ if (!error || typeof error !== "object") return null;
46
+ const code = error.code;
47
+ return typeof code === "string" ? code : null;
48
+ }
49
+ function needsReconciliation(error) {
50
+ return (/* @__PURE__ */ new Set(["actor_change_required", "generation_conflict", "operation_outcome_unknown"])).has(
51
+ errorCode(error) ?? ""
52
+ );
53
+ }
54
+ function BrowserAccountsProvider({
55
+ client,
56
+ onActorTransition,
57
+ children,
58
+ broadcastChannelName = DEFAULT_CHANNEL,
59
+ bootstrapLegacySession = false
60
+ }) {
61
+ const [projection, setProjection] = useState(null);
62
+ const [phase, setPhase] = useState("loading");
63
+ const [blockers, setBlockers] = useState([]);
64
+ const [error, setError] = useState(null);
65
+ const [transaction, setTransaction] = useState(null);
66
+ const projectionRef = useRef(projection);
67
+ const transactionRef = useRef(transaction);
68
+ const blockerInspectorsRef = useRef(/* @__PURE__ */ new Map());
69
+ const pendingRef = useRef(null);
70
+ const transactionTargetSlotIdRef = useRef(null);
71
+ const beginOperationRef = useRef(null);
72
+ const bootstrapOperationRef = useRef(null);
73
+ const completionOperationRef = useRef(null);
74
+ const cancelOperationRef = useRef(null);
75
+ const pendingCommitSequenceRef = useRef(null);
76
+ const invalidatedDuringPendingCommitRef = useRef(false);
77
+ const sequenceRef = useRef(0);
78
+ const transitionAbortRef = useRef(null);
79
+ const channelRef = useRef(null);
80
+ const crossTabActorHoldsRef = useRef(/* @__PURE__ */ new Map());
81
+ projectionRef.current = projection;
82
+ transactionRef.current = transaction;
83
+ const inspectBlockers = useCallback(() => {
84
+ const current = [...blockerInspectorsRef.current.entries()].map(([id, inspect]) => {
85
+ const blocker = inspect();
86
+ return blocker ? { ...blocker, id } : null;
87
+ }).filter((value2) => value2 !== null).sort((left, right) => left.id.localeCompare(right.id));
88
+ setBlockers(current);
89
+ return current;
90
+ }, []);
91
+ const publishActorHint = useCallback((next) => {
92
+ if (!next) return;
93
+ channelRef.current?.postMessage({
94
+ type: "actor-epoch-changed",
95
+ generation: next.generation,
96
+ actorEpoch: next.actorEpoch
97
+ });
98
+ }, []);
99
+ const publishActorHold = useCallback(
100
+ (transitionId, current) => {
101
+ channelRef.current?.postMessage({
102
+ type: "actor-transition-pending",
103
+ transitionId,
104
+ generation: current.generation,
105
+ actorEpoch: current.actorEpoch
106
+ });
107
+ },
108
+ []
109
+ );
110
+ const publishActorRelease = useCallback((transitionId) => {
111
+ channelRef.current?.postMessage({
112
+ type: "actor-transition-released",
113
+ transitionId
114
+ });
115
+ }, []);
116
+ const clearLoginTransactionState = useCallback(() => {
117
+ transactionRef.current = null;
118
+ beginOperationRef.current = null;
119
+ completionOperationRef.current = null;
120
+ cancelOperationRef.current = null;
121
+ transactionTargetSlotIdRef.current = null;
122
+ setTransaction(null);
123
+ }, []);
124
+ const settleActorTransition = useCallback(
125
+ async (kind, from, accepted, sequence, publish, authorityResetConfirmed = false) => {
126
+ let source = from;
127
+ let target = accepted;
128
+ if (!authorityResetConfirmed && from && target && isOlderProjection(target, from)) {
129
+ target = await client.getSessionSet();
130
+ if (sequenceRef.current !== sequence) return null;
131
+ if (isOlderProjection(target, from)) {
132
+ throw new Error("Browser account response regressed the accepted actor epoch");
133
+ }
134
+ }
135
+ if (sameSelectedActor(source, target)) {
136
+ setProjection(target);
137
+ setPhase("ready");
138
+ setError(null);
139
+ return target;
140
+ }
141
+ while (true) {
142
+ transitionAbortRef.current?.abort();
143
+ const controller = new AbortController();
144
+ transitionAbortRef.current = controller;
145
+ if (publish) publishActorHint(target);
146
+ setPhase("loading");
147
+ await onActorTransition({ kind, from: source, to: target, signal: controller.signal });
148
+ if (controller.signal.aborted || sequenceRef.current !== sequence) return null;
149
+ if (!target) {
150
+ setProjection(null);
151
+ setPhase("ready");
152
+ setError(null);
153
+ return null;
154
+ }
155
+ const confirmed = await client.reconcileSessionSetAuthority();
156
+ if (controller.signal.aborted || sequenceRef.current !== sequence) return null;
157
+ if (sameActor(target, confirmed) || sameSelectedActor(target, confirmed)) {
158
+ setProjection(confirmed);
159
+ setPhase("ready");
160
+ setError(null);
161
+ return confirmed;
162
+ }
163
+ source = target;
164
+ target = confirmed;
165
+ }
166
+ },
167
+ [client, onActorTransition, publishActorHint]
168
+ );
169
+ const reconcile = useCallback(
170
+ async (kind, publish = false) => {
171
+ const sequence = ++sequenceRef.current;
172
+ const before = projectionRef.current;
173
+ setPhase("loading");
174
+ let first = await client.reconcileSessionSetAuthority();
175
+ if (sequenceRef.current !== sequence) return null;
176
+ if (bootstrapLegacySession && first.mode === "dual" && first.selectedSlotId === null && first.slots.length === 0) {
177
+ const bootstrapOperation = bootstrapOperationRef.current?.generation === first.generation ? bootstrapOperationRef.current.operationId : operationId();
178
+ bootstrapOperationRef.current = {
179
+ generation: first.generation,
180
+ operationId: bootstrapOperation
181
+ };
182
+ try {
183
+ first = await client.bootstrapSessionSet({
184
+ operationId: bootstrapOperation,
185
+ expectedGeneration: first.generation
186
+ });
187
+ bootstrapOperationRef.current = null;
188
+ } catch (caught) {
189
+ if (!needsReconciliation(caught)) throw caught;
190
+ const reconciled = await client.getSessionSet();
191
+ if (sequenceRef.current !== sequence) return null;
192
+ if (reconciled.selectedSlotId === null || reconciled.slots.length === 0) throw caught;
193
+ first = reconciled;
194
+ bootstrapOperationRef.current = null;
195
+ }
196
+ }
197
+ if (sameActor(before, first) || sameSelectedActor(before, first)) {
198
+ setProjection(first);
199
+ setPhase("ready");
200
+ setError(null);
201
+ return first;
202
+ }
203
+ if (kind === "cross_tab") clearLoginTransactionState();
204
+ return await settleActorTransition(kind, before, first, sequence, publish, true);
205
+ },
206
+ [bootstrapLegacySession, clearLoginTransactionState, client, settleActorTransition]
207
+ );
208
+ const fail = useCallback(
209
+ async (caught, kind) => {
210
+ const nextError = accountError(caught);
211
+ if (needsReconciliation(caught)) {
212
+ try {
213
+ await reconcile(kind);
214
+ } catch {
215
+ }
216
+ }
217
+ setError(nextError);
218
+ setPhase("recoverable_error");
219
+ throw nextError;
220
+ },
221
+ [reconcile]
222
+ );
223
+ const executePending = useCallback(
224
+ async (pending) => {
225
+ const blocked = inspectBlockers();
226
+ if (blocked.length > 0) {
227
+ pendingRef.current = pending;
228
+ setPhase("blocked");
229
+ return false;
230
+ }
231
+ const current = projectionRef.current;
232
+ if (!current) throw new Error("Browser session set is not loaded");
233
+ const expectedGeneration = pending.expectedGeneration ?? current.generation;
234
+ pending.expectedGeneration = expectedGeneration;
235
+ const sequence = ++sequenceRef.current;
236
+ pendingCommitSequenceRef.current = sequence;
237
+ invalidatedDuringPendingCommitRef.current = false;
238
+ pendingRef.current = pending;
239
+ setPhase("committing");
240
+ setError(null);
241
+ const changesActor = pending.changesActor(current);
242
+ if (changesActor) {
243
+ publishActorHold(pending.operationId, current);
244
+ await yieldToCrossTabActorHold();
245
+ }
246
+ try {
247
+ let accepted;
248
+ try {
249
+ accepted = await pending.execute(expectedGeneration);
250
+ } catch (caught) {
251
+ if (errorCode(caught) !== "operation_outcome_unknown") throw caught;
252
+ accepted = await pending.execute(expectedGeneration);
253
+ }
254
+ if (sequenceRef.current !== sequence) return false;
255
+ let authorityResetConfirmed = pending.kind === "logout_all";
256
+ if (invalidatedDuringPendingCommitRef.current) {
257
+ accepted = await client.reconcileSessionSetAuthority();
258
+ authorityResetConfirmed = true;
259
+ if (sequenceRef.current !== sequence) return false;
260
+ }
261
+ pendingRef.current = null;
262
+ if (!authorityResetConfirmed && sameSelectedActor(current, accepted)) {
263
+ setProjection(accepted);
264
+ setPhase("ready");
265
+ setError(null);
266
+ } else {
267
+ await settleActorTransition(
268
+ pending.kind,
269
+ current,
270
+ accepted,
271
+ sequence,
272
+ true,
273
+ authorityResetConfirmed
274
+ );
275
+ }
276
+ return true;
277
+ } catch (caught) {
278
+ return await fail(caught, pending.kind);
279
+ } finally {
280
+ if (changesActor) publishActorRelease(pending.operationId);
281
+ if (pendingCommitSequenceRef.current === sequence) {
282
+ pendingCommitSequenceRef.current = null;
283
+ invalidatedDuringPendingCommitRef.current = false;
284
+ }
285
+ }
286
+ },
287
+ [client, fail, inspectBlockers, publishActorHold, publishActorRelease, settleActorTransition]
288
+ );
289
+ const refresh = useCallback(async () => {
290
+ try {
291
+ return await reconcile("cross_tab");
292
+ } catch (caught) {
293
+ const nextError = accountError(caught);
294
+ setError(nextError);
295
+ setPhase("recoverable_error");
296
+ throw nextError;
297
+ }
298
+ }, [reconcile]);
299
+ const beginNeutralActorInvalidation = useCallback(
300
+ async (before) => {
301
+ transitionAbortRef.current?.abort();
302
+ const controller = new AbortController();
303
+ transitionAbortRef.current = controller;
304
+ projectionRef.current = null;
305
+ setProjection(null);
306
+ clearLoginTransactionState();
307
+ setPhase("loading");
308
+ setError(null);
309
+ await onActorTransition({
310
+ kind: "cross_tab",
311
+ from: before,
312
+ to: null,
313
+ signal: controller.signal
314
+ });
315
+ return controller;
316
+ },
317
+ [clearLoginTransactionState, onActorTransition]
318
+ );
319
+ const invalidateActor = useCallback(async () => {
320
+ if (pendingCommitSequenceRef.current !== null) {
321
+ invalidatedDuringPendingCommitRef.current = true;
322
+ await beginNeutralActorInvalidation(projectionRef.current);
323
+ return null;
324
+ }
325
+ const sequence = ++sequenceRef.current;
326
+ const before = projectionRef.current;
327
+ try {
328
+ const neutral = await beginNeutralActorInvalidation(before);
329
+ if (neutral.signal.aborted || sequenceRef.current !== sequence) return null;
330
+ const accepted = await client.reconcileSessionSetAuthority();
331
+ if (sequenceRef.current !== sequence) return null;
332
+ return await settleActorTransition("cross_tab", before, accepted, sequence, false, true);
333
+ } catch (caught) {
334
+ if (sequenceRef.current !== sequence) return null;
335
+ const nextError = accountError(caught);
336
+ setError(nextError);
337
+ setPhase("recoverable_error");
338
+ throw nextError;
339
+ }
340
+ }, [beginNeutralActorInvalidation, client, settleActorTransition]);
341
+ const registerTransitionBlocker = useCallback((id, inspect) => {
342
+ if (!id.trim()) throw new Error("Browser account transition blocker id is required");
343
+ blockerInspectorsRef.current.set(id, inspect);
344
+ return () => {
345
+ if (blockerInspectorsRef.current.get(id) === inspect) {
346
+ blockerInspectorsRef.current.delete(id);
347
+ }
348
+ };
349
+ }, []);
350
+ const continueTransition = useCallback(async () => {
351
+ const pending = pendingRef.current;
352
+ return pending ? await executePending(pending) : false;
353
+ }, [executePending]);
354
+ const cancelPendingTransition = useCallback(() => {
355
+ pendingRef.current = null;
356
+ setBlockers([]);
357
+ setPhase(projectionRef.current ? "ready" : "loading");
358
+ }, []);
359
+ const beginLogin = useCallback(
360
+ async (kind, slotId, returnIntent) => {
361
+ const current = projectionRef.current;
362
+ if (!current) throw new Error("Browser session set is not loaded");
363
+ if (kind === "reauth" && slotId === current.selectedSlotId && inspectBlockers().length > 0) {
364
+ setPhase("blocked");
365
+ throw new Error("Settle account transition blockers before re-authenticating");
366
+ }
367
+ const sequence = ++sequenceRef.current;
368
+ setPhase("committing");
369
+ setError(null);
370
+ const signature = JSON.stringify([
371
+ kind,
372
+ slotId ?? null,
373
+ returnIntent ?? null,
374
+ current.generation
375
+ ]);
376
+ const beginOperation = beginOperationRef.current?.signature === signature ? beginOperationRef.current.operationId : operationId();
377
+ beginOperationRef.current = { signature, operationId: beginOperation };
378
+ let next;
379
+ try {
380
+ next = await client.beginLoginTransaction({
381
+ operationId: beginOperation,
382
+ expectedGeneration: current.generation,
383
+ kind,
384
+ ...slotId ? { slotId } : {},
385
+ ...returnIntent ? { returnIntent } : {}
386
+ });
387
+ } catch (caught) {
388
+ if (sequenceRef.current !== sequence) {
389
+ if (beginOperationRef.current?.operationId === beginOperation) {
390
+ beginOperationRef.current = null;
391
+ }
392
+ throw supersededOperationError();
393
+ }
394
+ return await fail(caught, kind);
395
+ }
396
+ if (sequenceRef.current !== sequence) {
397
+ if (beginOperationRef.current?.operationId === beginOperation) {
398
+ beginOperationRef.current = null;
399
+ }
400
+ throw supersededOperationError();
401
+ }
402
+ beginOperationRef.current = null;
403
+ transactionTargetSlotIdRef.current = slotId ?? null;
404
+ completionOperationRef.current = null;
405
+ cancelOperationRef.current = null;
406
+ setTransaction(next);
407
+ setPhase("ready");
408
+ return next;
409
+ },
410
+ [client, fail, inspectBlockers]
411
+ );
412
+ const beginAdd = useCallback(
413
+ async (returnIntent) => await beginLogin("add", void 0, returnIntent),
414
+ [beginLogin]
415
+ );
416
+ const beginReauth = useCallback(
417
+ async (slotId, returnIntent) => await beginLogin("reauth", slotId, returnIntent),
418
+ [beginLogin]
419
+ );
420
+ const completeEmailPassword = useCallback(
421
+ async (input) => {
422
+ const current = projectionRef.current;
423
+ const activeTransaction = transactionRef.current;
424
+ if (!current || !activeTransaction) throw new Error("No browser login transaction is active");
425
+ const selectedReauth = activeTransaction.kind === "reauth" && transactionTargetSlotIdRef.current !== null && transactionTargetSlotIdRef.current === current.selectedSlotId;
426
+ if (selectedReauth && inspectBlockers().length > 0) {
427
+ setPhase("blocked");
428
+ return false;
429
+ }
430
+ const sequence = ++sequenceRef.current;
431
+ setPhase("committing");
432
+ setError(null);
433
+ const completionOperation = completionOperationRef.current ?? {
434
+ operationId: operationId(),
435
+ expectedGeneration: current.generation
436
+ };
437
+ completionOperationRef.current = completionOperation;
438
+ try {
439
+ const completed = await client.completeEmailPasswordTransaction({
440
+ operationId: completionOperation.operationId,
441
+ expectedGeneration: completionOperation.expectedGeneration,
442
+ transactionId: activeTransaction.id,
443
+ email: input.email,
444
+ password: input.password
445
+ });
446
+ if (sequenceRef.current !== sequence) return false;
447
+ completionOperationRef.current = null;
448
+ cancelOperationRef.current = null;
449
+ transactionTargetSlotIdRef.current = null;
450
+ setTransaction(null);
451
+ const actorChanged = completed.projection.actorEpoch !== current.actorEpoch;
452
+ if (actorChanged) {
453
+ await settleActorTransition(
454
+ activeTransaction.kind,
455
+ current,
456
+ completed.projection,
457
+ sequence,
458
+ true
459
+ );
460
+ } else {
461
+ setProjection(completed.projection);
462
+ setPhase("ready");
463
+ }
464
+ return true;
465
+ } catch (caught) {
466
+ return await fail(caught, activeTransaction.kind);
467
+ }
468
+ },
469
+ [client, fail, inspectBlockers, settleActorTransition]
470
+ );
471
+ const settleExternalLoginTransaction = useCallback(
472
+ async (transactionId) => {
473
+ const activeTransaction = transactionRef.current;
474
+ if (!activeTransaction || activeTransaction.id !== transactionId) return false;
475
+ try {
476
+ await reconcile(activeTransaction.kind, true);
477
+ if (transactionRef.current?.id !== transactionId) return false;
478
+ completionOperationRef.current = null;
479
+ cancelOperationRef.current = null;
480
+ transactionTargetSlotIdRef.current = null;
481
+ setTransaction(null);
482
+ return true;
483
+ } catch (caught) {
484
+ const nextError = accountError(caught);
485
+ setError(nextError);
486
+ setPhase("recoverable_error");
487
+ throw nextError;
488
+ }
489
+ },
490
+ [reconcile]
491
+ );
492
+ const cancelLoginTransaction = useCallback(async () => {
493
+ const current = projectionRef.current;
494
+ const activeTransaction = transactionRef.current;
495
+ if (!current || !activeTransaction) return;
496
+ const sequence = ++sequenceRef.current;
497
+ setPhase("committing");
498
+ const cancelOperation = cancelOperationRef.current ?? {
499
+ operationId: operationId(),
500
+ expectedGeneration: current.generation
501
+ };
502
+ cancelOperationRef.current = cancelOperation;
503
+ let next;
504
+ try {
505
+ next = await client.cancelLoginTransaction({
506
+ operationId: cancelOperation.operationId,
507
+ expectedGeneration: cancelOperation.expectedGeneration,
508
+ transactionId: activeTransaction.id
509
+ });
510
+ } catch (caught) {
511
+ if (sequenceRef.current !== sequence) {
512
+ if (cancelOperationRef.current?.operationId === cancelOperation.operationId) {
513
+ cancelOperationRef.current = null;
514
+ }
515
+ throw supersededOperationError();
516
+ }
517
+ await fail(caught, activeTransaction.kind);
518
+ return;
519
+ }
520
+ if (sequenceRef.current !== sequence) {
521
+ if (cancelOperationRef.current?.operationId === cancelOperation.operationId) {
522
+ cancelOperationRef.current = null;
523
+ }
524
+ throw supersededOperationError();
525
+ }
526
+ clearLoginTransactionState();
527
+ setProjection(next);
528
+ setPhase("ready");
529
+ setError(null);
530
+ }, [clearLoginTransactionState, client, fail]);
531
+ const selectSlot = useCallback(
532
+ async (slotId) => {
533
+ const id = operationId();
534
+ return await executePending({
535
+ kind: "select",
536
+ operationId: id,
537
+ expectedGeneration: null,
538
+ changesActor: (current) => current.selectedSlotId !== slotId,
539
+ execute: async (expectedGeneration) => await client.selectLoginSlot({
540
+ operationId: id,
541
+ expectedGeneration,
542
+ slotId
543
+ })
544
+ });
545
+ },
546
+ [client, executePending]
547
+ );
548
+ const logoutSlot = useCallback(
549
+ async (slotId, replacementSlotId) => {
550
+ const id = operationId();
551
+ return await executePending({
552
+ kind: "logout_one",
553
+ operationId: id,
554
+ expectedGeneration: null,
555
+ changesActor: (current) => current.selectedSlotId === slotId,
556
+ execute: async (expectedGeneration) => await client.logoutLoginSlot({
557
+ operationId: id,
558
+ expectedGeneration,
559
+ slotId,
560
+ replacementSlotId
561
+ })
562
+ });
563
+ },
564
+ [client, executePending]
565
+ );
566
+ const logoutAll = useCallback(async () => {
567
+ const id = operationId();
568
+ return await executePending({
569
+ kind: "logout_all",
570
+ operationId: id,
571
+ expectedGeneration: null,
572
+ // Logout-all always rotates the browser-set authority, even when the set
573
+ // is neutral but still contains slots. Peers must therefore enter the
574
+ // same precommit hold and reconciliation boundary unconditionally.
575
+ changesActor: () => true,
576
+ execute: async (expectedGeneration) => {
577
+ await client.logoutSessionSet({ operationId: id, expectedGeneration });
578
+ return await client.getSessionSet();
579
+ }
580
+ });
581
+ }, [client, executePending]);
582
+ const resolveDeepLink = useCallback(
583
+ async (path) => await client.resolveDeepLink({ path }),
584
+ [client]
585
+ );
586
+ useEffect(() => {
587
+ const sequence = sequenceRef;
588
+ const transitionAbort = transitionAbortRef;
589
+ void refresh().catch((caught) => {
590
+ setError(accountError(caught));
591
+ setPhase("recoverable_error");
592
+ });
593
+ return () => {
594
+ ++sequence.current;
595
+ transitionAbort.current?.abort();
596
+ };
597
+ }, [client, refresh]);
598
+ useEffect(() => {
599
+ if (!broadcastChannelName || typeof BroadcastChannel === "undefined") return;
600
+ const channel = new BroadcastChannel(broadcastChannelName);
601
+ const actorHolds = crossTabActorHoldsRef.current;
602
+ channelRef.current = channel;
603
+ channel.onmessage = (event) => {
604
+ const value2 = event.data;
605
+ if (!value2 || typeof value2 !== "object") return;
606
+ const hint = value2;
607
+ if (hint.type === "actor-transition-pending") {
608
+ if (typeof hint.transitionId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
609
+ hint.transitionId
610
+ ) || typeof hint.actorEpoch !== "string" || typeof hint.generation !== "string" || crossTabActorHoldsRef.current.has(hint.transitionId)) {
611
+ return;
612
+ }
613
+ const current = projectionRef.current;
614
+ if (current && (current.actorEpoch !== hint.actorEpoch || current.generation !== hint.generation)) {
615
+ return;
616
+ }
617
+ if (!current && crossTabActorHoldsRef.current.size === 0) return;
618
+ const firstHold = crossTabActorHoldsRef.current.size === 0;
619
+ const timeout = setTimeout(() => {
620
+ if (!crossTabActorHoldsRef.current.delete(hint.transitionId)) return;
621
+ if (crossTabActorHoldsRef.current.size === 0) {
622
+ void invalidateActor().catch(() => void 0);
623
+ }
624
+ }, CROSS_TAB_ACTOR_HOLD_MS);
625
+ crossTabActorHoldsRef.current.set(hint.transitionId, timeout);
626
+ if (firstHold) {
627
+ if (pendingCommitSequenceRef.current !== null) {
628
+ invalidatedDuringPendingCommitRef.current = true;
629
+ } else {
630
+ ++sequenceRef.current;
631
+ }
632
+ void beginNeutralActorInvalidation(current).catch(() => void 0);
633
+ }
634
+ return;
635
+ }
636
+ if (hint.type === "actor-transition-released") {
637
+ if (typeof hint.transitionId !== "string") return;
638
+ const timeout = crossTabActorHoldsRef.current.get(hint.transitionId);
639
+ if (timeout === void 0) return;
640
+ clearTimeout(timeout);
641
+ crossTabActorHoldsRef.current.delete(hint.transitionId);
642
+ if (crossTabActorHoldsRef.current.size === 0) {
643
+ void invalidateActor().catch(() => void 0);
644
+ }
645
+ return;
646
+ }
647
+ if (hint.type !== "actor-epoch-changed" || typeof hint.actorEpoch !== "string" || typeof hint.generation !== "string" || hint.actorEpoch === projectionRef.current?.actorEpoch && hint.generation === projectionRef.current?.generation) {
648
+ return;
649
+ }
650
+ for (const timeout of crossTabActorHoldsRef.current.values()) clearTimeout(timeout);
651
+ crossTabActorHoldsRef.current.clear();
652
+ void invalidateActor().catch(() => void 0);
653
+ };
654
+ return () => {
655
+ for (const timeout of actorHolds.values()) clearTimeout(timeout);
656
+ actorHolds.clear();
657
+ channel.close();
658
+ if (channelRef.current === channel) channelRef.current = null;
659
+ };
660
+ }, [beginNeutralActorInvalidation, broadcastChannelName, invalidateActor]);
661
+ const value = useMemo(
662
+ () => ({
663
+ projection,
664
+ phase,
665
+ blockers,
666
+ error,
667
+ transaction,
668
+ hasPendingTransition: pendingRef.current !== null,
669
+ refresh,
670
+ invalidateActor,
671
+ registerTransitionBlocker,
672
+ continueTransition,
673
+ cancelPendingTransition,
674
+ beginAdd,
675
+ beginReauth,
676
+ completeEmailPassword,
677
+ settleExternalLoginTransaction,
678
+ cancelLoginTransaction,
679
+ selectSlot,
680
+ logoutSlot,
681
+ logoutAll,
682
+ resolveDeepLink
683
+ }),
684
+ [
685
+ projection,
686
+ phase,
687
+ blockers,
688
+ error,
689
+ transaction,
690
+ refresh,
691
+ invalidateActor,
692
+ registerTransitionBlocker,
693
+ continueTransition,
694
+ cancelPendingTransition,
695
+ beginAdd,
696
+ beginReauth,
697
+ completeEmailPassword,
698
+ settleExternalLoginTransaction,
699
+ cancelLoginTransaction,
700
+ selectSlot,
701
+ logoutSlot,
702
+ logoutAll,
703
+ resolveDeepLink
704
+ ]
705
+ );
706
+ return /* @__PURE__ */ jsx(BrowserAccountsContext.Provider, { value, children });
707
+ }
708
+ function useBrowserAccounts() {
709
+ const context = useContext(BrowserAccountsContext);
710
+ if (!context) {
711
+ throw new Error(
712
+ "@opengeni/react/accounts: wrap the tree in <BrowserAccountsProvider> before using account hooks"
713
+ );
714
+ }
715
+ return context;
716
+ }
717
+ function useOptionalBrowserAccounts() {
718
+ return useContext(BrowserAccountsContext);
719
+ }
720
+ function useBrowserAccountTransitionBlocker(id, inspect) {
721
+ const { registerTransitionBlocker } = useBrowserAccounts();
722
+ const inspectRef = useRef(inspect);
723
+ inspectRef.current = inspect;
724
+ useEffect(
725
+ () => registerTransitionBlocker(id, () => inspectRef.current()),
726
+ [id, registerTransitionBlocker]
727
+ );
728
+ }
729
+ function useOptionalBrowserAccountTransitionBlocker(id, inspect) {
730
+ const context = useContext(BrowserAccountsContext);
731
+ const registerTransitionBlocker = context?.registerTransitionBlocker;
732
+ const inspectRef = useRef(inspect);
733
+ inspectRef.current = inspect;
734
+ useEffect(() => {
735
+ if (!registerTransitionBlocker) return;
736
+ return registerTransitionBlocker(id, () => inspectRef.current());
737
+ }, [id, registerTransitionBlocker]);
738
+ }
739
+ export {
740
+ BrowserAccountsProvider,
741
+ useBrowserAccountTransitionBlocker,
742
+ useBrowserAccounts,
743
+ useOptionalBrowserAccountTransitionBlocker,
744
+ useOptionalBrowserAccounts
745
+ };
746
+ //# sourceMappingURL=accounts.js.map