@oxyhq/core 12.0.0 → 12.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 (67) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/i18n/locales/ar-SA.json +26 -2
  3. package/dist/cjs/i18n/locales/ca-ES.json +26 -2
  4. package/dist/cjs/i18n/locales/de-DE.json +26 -2
  5. package/dist/cjs/i18n/locales/en-US.json +25 -3
  6. package/dist/cjs/i18n/locales/es-ES.json +25 -3
  7. package/dist/cjs/i18n/locales/fr-FR.json +26 -2
  8. package/dist/cjs/i18n/locales/it-IT.json +26 -2
  9. package/dist/cjs/i18n/locales/ja-JP.json +26 -2
  10. package/dist/cjs/i18n/locales/ko-KR.json +26 -2
  11. package/dist/cjs/i18n/locales/locales/ar-SA.json +26 -2
  12. package/dist/cjs/i18n/locales/locales/ca-ES.json +26 -2
  13. package/dist/cjs/i18n/locales/locales/de-DE.json +26 -2
  14. package/dist/cjs/i18n/locales/locales/en-US.json +25 -3
  15. package/dist/cjs/i18n/locales/locales/es-ES.json +25 -3
  16. package/dist/cjs/i18n/locales/locales/fr-FR.json +26 -2
  17. package/dist/cjs/i18n/locales/locales/it-IT.json +26 -2
  18. package/dist/cjs/i18n/locales/locales/ja-JP.json +26 -2
  19. package/dist/cjs/i18n/locales/locales/ko-KR.json +26 -2
  20. package/dist/cjs/i18n/locales/locales/pt-PT.json +26 -2
  21. package/dist/cjs/i18n/locales/locales/zh-CN.json +26 -2
  22. package/dist/cjs/i18n/locales/pt-PT.json +26 -2
  23. package/dist/cjs/i18n/locales/zh-CN.json +26 -2
  24. package/dist/cjs/session/accountDialogController.js +83 -21
  25. package/dist/esm/.tsbuildinfo +1 -1
  26. package/dist/esm/i18n/locales/ar-SA.json +26 -2
  27. package/dist/esm/i18n/locales/ca-ES.json +26 -2
  28. package/dist/esm/i18n/locales/de-DE.json +26 -2
  29. package/dist/esm/i18n/locales/en-US.json +25 -3
  30. package/dist/esm/i18n/locales/es-ES.json +25 -3
  31. package/dist/esm/i18n/locales/fr-FR.json +26 -2
  32. package/dist/esm/i18n/locales/it-IT.json +26 -2
  33. package/dist/esm/i18n/locales/ja-JP.json +26 -2
  34. package/dist/esm/i18n/locales/ko-KR.json +26 -2
  35. package/dist/esm/i18n/locales/locales/ar-SA.json +26 -2
  36. package/dist/esm/i18n/locales/locales/ca-ES.json +26 -2
  37. package/dist/esm/i18n/locales/locales/de-DE.json +26 -2
  38. package/dist/esm/i18n/locales/locales/en-US.json +25 -3
  39. package/dist/esm/i18n/locales/locales/es-ES.json +25 -3
  40. package/dist/esm/i18n/locales/locales/fr-FR.json +26 -2
  41. package/dist/esm/i18n/locales/locales/it-IT.json +26 -2
  42. package/dist/esm/i18n/locales/locales/ja-JP.json +26 -2
  43. package/dist/esm/i18n/locales/locales/ko-KR.json +26 -2
  44. package/dist/esm/i18n/locales/locales/pt-PT.json +26 -2
  45. package/dist/esm/i18n/locales/locales/zh-CN.json +26 -2
  46. package/dist/esm/i18n/locales/pt-PT.json +26 -2
  47. package/dist/esm/i18n/locales/zh-CN.json +26 -2
  48. package/dist/esm/session/accountDialogController.js +83 -21
  49. package/dist/types/.tsbuildinfo +1 -1
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/types/session/accountDialogController.d.ts +81 -16
  52. package/package.json +1 -1
  53. package/src/i18n/locales/ar-SA.json +26 -2
  54. package/src/i18n/locales/ca-ES.json +26 -2
  55. package/src/i18n/locales/de-DE.json +26 -2
  56. package/src/i18n/locales/en-US.json +25 -3
  57. package/src/i18n/locales/es-ES.json +25 -3
  58. package/src/i18n/locales/fr-FR.json +26 -2
  59. package/src/i18n/locales/it-IT.json +26 -2
  60. package/src/i18n/locales/ja-JP.json +26 -2
  61. package/src/i18n/locales/ko-KR.json +26 -2
  62. package/src/i18n/locales/pt-PT.json +26 -2
  63. package/src/i18n/locales/zh-CN.json +26 -2
  64. package/src/index.ts +1 -0
  65. package/src/session/__tests__/accountDialogController.test.ts +79 -2
  66. package/src/session/__tests__/accountProjection.test.ts +30 -0
  67. package/src/session/accountDialogController.ts +128 -27
@@ -13,17 +13,23 @@
13
13
  * - the unified account list (via {@link projectSwitchableAccounts}), fetched
14
14
  * from `SessionClient` state ∪ `oxyServices.listAccounts()` and hydrated
15
15
  * with `oxyServices.getUsersByIds()`;
16
- * - the dialog `view` state machine (`accounts` | `signin` | `qr` | `add`);
16
+ * - the dialog `view` state machine (`accounts` | `signin` | `qr` | `add` |
17
+ * `signup`);
17
18
  * - `switchTo` (the uniform switch: `SessionClient.switchAccount` for an
18
19
  * account already on the device, `oxyServices.switchToAccount` to mint on
19
20
  * first entry into a graph account — reusing the existing SDK primitives, no
20
21
  * new switch path);
21
22
  * - the "Sign in with Oxy" device flow (same-device shared-keychain via
22
23
  * `oxyServices.signInWithSharedIdentity`, else the cross-device QR handoff
23
- * via `startCommonsSignIn` → poll → `claimSessionByToken`).
24
+ * via `startCommonsSignIn` → poll → `claimSessionByToken`);
25
+ * - `commonsAvailability` — whether Commons is installed on this device
26
+ * (native only, via the injected `canOpenApp` probe), so the QR view can
27
+ * offer a "Get Commons" fallback instead of a same-device dead end.
24
28
  *
25
29
  * Sign-in is passkey (WebAuthn) or the Commons QR / shared-keychain handoff —
26
- * password, social login, and 2FA were removed ecosystem-wide.
30
+ * password, social login, and 2FA were removed ecosystem-wide. Account
31
+ * creation (`signup` view) is the same two identity backends: a passkey
32
+ * ceremony on web, or a Commons-created identity.
27
33
  */
28
34
  Object.defineProperty(exports, "__esModule", { value: true });
29
35
  exports.AccountDialogController = void 0;
@@ -67,6 +73,7 @@ class AccountDialogController {
67
73
  this.error = null;
68
74
  this.switchingAccountId = null;
69
75
  this.signIn = IDLE_SIGN_IN;
76
+ this.commonsAvailability = 'unknown';
70
77
  // --- Sign-in device-flow bookkeeping ---
71
78
  /** The secret device-flow token of the active QR flow (never surfaced). */
72
79
  this.signInToken = null;
@@ -95,6 +102,7 @@ class AccountDialogController {
95
102
  this.clientId = options.clientId ?? null;
96
103
  this.locale = options.locale;
97
104
  this.commitSession = options.commitSession;
105
+ this.commitSwitchedSession = options.commitSwitchedSession;
98
106
  this.onSignedIn = options.onSignedIn;
99
107
  this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
100
108
  this.openUrl = options.openUrl;
@@ -151,6 +159,11 @@ class AccountDialogController {
151
159
  // bearer is already planted (warm start); when signed out (cold boot before
152
160
  // restore) it re-projects from device state and makes NO private call.
153
161
  void this.refresh();
162
+ // Eager, cached Commons-availability probe (native only — a no-op when no
163
+ // `canOpenApp` was injected). It's a cheap local OS check, so by the time a
164
+ // user actually opens the sign-in entry it has almost always resolved —
165
+ // `showQr`'s own lazy probe below is only the safety net for the rare race.
166
+ void this.resolveCommonsAvailability();
154
167
  }
155
168
  /**
156
169
  * Stop driving the dialog: unsubscribe from `SessionClient` and tear down the
@@ -234,6 +247,10 @@ class AccountDialogController {
234
247
  add() {
235
248
  this.setView('add');
236
249
  }
250
+ /** Switch to the "create account" view (passkey / Commons signup entry). */
251
+ startSignup() {
252
+ this.setView('signup');
253
+ }
237
254
  // =========================================================================
238
255
  // Account list
239
256
  // =========================================================================
@@ -379,7 +396,11 @@ class AccountDialogController {
379
396
  expiresAt: result.expiresAt,
380
397
  user: result.user,
381
398
  accessToken: result.accessToken,
382
- }, result.user);
399
+ }, result.user,
400
+ // A switch is IN-PLACE: commit without the hub-sync redirect (the
401
+ // device is already known/synced). Cross-tab/app propagation rides the
402
+ // server's `session_state` socket broadcast, not a navigation.
403
+ { fromSwitch: true });
383
404
  }
384
405
  // Re-project + refetch immediately; the subscription also fires.
385
406
  await this.refresh();
@@ -447,32 +468,63 @@ class AccountDialogController {
447
468
  // connect, so it now runs at the slow fallback cadence.
448
469
  this.openAuthSessionSocket(handle.sessionToken);
449
470
  this.scheduleNextPoll(handle.sessionToken);
450
- // Same-device convenience: if Commons is installed (native only — `canOpenApp`
451
- // is undefined/false on web), deep-link straight into its approve screen with
452
- // the same `oxycommons://approve?...` payload the QR encodes. The QR + polling
471
+ // Same-device convenience: if Commons is confirmed installed (native
472
+ // only stays `'unknown'` on web, where this never opens anything),
473
+ // deep-link straight into its approve screen with the same
474
+ // `oxycommons://approve?...` payload the QR encodes. The QR + polling
453
475
  // stay live as the fallback, so a user who dismisses the app-open still
454
476
  // completes the sign-in by scanning.
455
- void this.maybeOpenCommons(handle.qrPayload);
477
+ void this.deepLinkIntoCommonsIfAvailable(handle.qrPayload);
456
478
  }
457
479
  catch (error) {
458
480
  this.setSignIn({ ...IDLE_SIGN_IN, phase: 'error', error: errorMessage(error) });
459
481
  }
460
482
  }
461
483
  /**
462
- * When a `canOpenApp` probe is injected and reports Commons installed, open the
463
- * approve deep link via the injected `openUrl`. Best-effort and non-blocking: a
464
- * probe/open failure is logged and swallowed the QR/polling fallback remains.
484
+ * Resolve whether Commons is installed on this device via the injected
485
+ * `canOpenApp` probe, updating {@link commonsAvailability} as durable,
486
+ * observable snapshot state. Native onlya no-op when `canOpenApp` was
487
+ * not injected (web), where `commonsAvailability` stays `'unknown'` forever
488
+ * and the QR view renders unconditionally (no gating).
489
+ *
490
+ * Replaces the old `maybeOpenCommons` fire-and-forget probe, whose outcome
491
+ * was only ever reflected by whether Commons silently opened — a probe
492
+ * failure or "not installed" answer was swallowed into a debug log with no
493
+ * way for the UI to react. `commonsAvailability` fixes that.
465
494
  */
466
- async maybeOpenCommons(qrPayload) {
467
- if (!this.canOpenApp || !this.openUrl)
495
+ async resolveCommonsAvailability() {
496
+ if (!this.canOpenApp)
468
497
  return;
498
+ this.commonsAvailability = 'checking';
499
+ this.emit();
500
+ let available = false;
469
501
  try {
470
- if (await this.canOpenApp(COMMONS_APP_SCHEME)) {
471
- this.openUrl(qrPayload);
472
- }
502
+ available = await this.canOpenApp(COMMONS_APP_SCHEME);
473
503
  }
474
504
  catch (error) {
475
- logger_1.logger.debug('[AccountDialogController] Commons deep-link probe failed (QR fallback active)', { component: 'AccountDialogController' }, error);
505
+ logger_1.logger.debug('[AccountDialogController] Commons availability probe failed', { component: 'AccountDialogController' }, error);
506
+ available = false; // fail-closed — treat a probe error as "not installed"
507
+ }
508
+ this.commonsAvailability = available ? 'available' : 'unavailable';
509
+ this.emit();
510
+ }
511
+ /**
512
+ * When Commons is confirmed installed, deep-link straight into its approve
513
+ * screen via the injected `openUrl` with the same `oxycommons://approve?...`
514
+ * payload the QR encodes. Best-effort and non-blocking — the QR/polling
515
+ * fallback stays live regardless of the outcome here.
516
+ */
517
+ async deepLinkIntoCommonsIfAvailable(qrPayload) {
518
+ if (!this.openUrl)
519
+ return;
520
+ if (this.commonsAvailability === 'unknown' || this.commonsAvailability === 'checking') {
521
+ // The eager `start()` probe hasn't resolved yet (or was never run, e.g.
522
+ // `showQr` called without a prior `start()`) — resolve it now rather
523
+ // than skipping the deep link.
524
+ await this.resolveCommonsAvailability();
525
+ }
526
+ if (this.commonsAvailability === 'available') {
527
+ this.openUrl(qrPayload);
476
528
  }
477
529
  }
478
530
  /** Tear down the active sign-in device flow (timers + socket + token) and reset to idle. */
@@ -595,12 +647,21 @@ class AccountDialogController {
595
647
  }
596
648
  /**
597
649
  * Register a token-planted session into the device set. Prefers the
598
- * consumer's `commitSession` (durable persist + hydration); falls back to
650
+ * consumer's commit funnel (durable persist + hydration); falls back to
599
651
  * `SessionClient.registerAndActivate` (registration + activation only).
652
+ *
653
+ * A SWITCH (`opts.fromSwitch`) uses the IN-PLACE `commitSwitchedSession` funnel
654
+ * so it never runs the cross-origin hub-sync redirect; a SIGN-IN uses
655
+ * `commitSession` (which may hub-sync on an official web origin). When the
656
+ * switch funnel is not wired it falls back to the sign-in funnel, then to
657
+ * `registerAndActivate`.
600
658
  */
601
- async commitAuthorizedSession(session, user) {
602
- if (this.commitSession) {
603
- await this.commitSession(session);
659
+ async commitAuthorizedSession(session, user, opts) {
660
+ const commit = opts?.fromSwitch
661
+ ? this.commitSwitchedSession ?? this.commitSession
662
+ : this.commitSession;
663
+ if (commit) {
664
+ await commit(session);
604
665
  }
605
666
  else {
606
667
  await this.sessionClient.registerAndActivate(user.id);
@@ -712,6 +773,7 @@ class AccountDialogController {
712
773
  error: this.error,
713
774
  switchingAccountId: this.switchingAccountId,
714
775
  signIn: this.signIn,
776
+ commonsAvailability: this.commonsAvailability,
715
777
  };
716
778
  }
717
779
  /** Recompute the snapshot and notify subscribers. */