@oxyhq/core 5.1.1 → 5.2.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 (114) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/AuthManager.js +27 -5
  3. package/dist/cjs/OxyServices.base.js +71 -0
  4. package/dist/cjs/i18n/locales/ar-SA.json +1 -0
  5. package/dist/cjs/i18n/locales/ca-ES.json +1 -0
  6. package/dist/cjs/i18n/locales/de-DE.json +1 -0
  7. package/dist/cjs/i18n/locales/en-US.json +1 -0
  8. package/dist/cjs/i18n/locales/es-ES.json +1 -0
  9. package/dist/cjs/i18n/locales/fr-FR.json +1 -0
  10. package/dist/cjs/i18n/locales/it-IT.json +1 -0
  11. package/dist/cjs/i18n/locales/ja-JP.json +1 -0
  12. package/dist/cjs/i18n/locales/ko-KR.json +1 -0
  13. package/dist/cjs/i18n/locales/locales/ar-SA.json +1 -0
  14. package/dist/cjs/i18n/locales/locales/ca-ES.json +1 -0
  15. package/dist/cjs/i18n/locales/locales/de-DE.json +1 -0
  16. package/dist/cjs/i18n/locales/locales/en-US.json +1 -0
  17. package/dist/cjs/i18n/locales/locales/es-ES.json +1 -0
  18. package/dist/cjs/i18n/locales/locales/fr-FR.json +1 -0
  19. package/dist/cjs/i18n/locales/locales/it-IT.json +1 -0
  20. package/dist/cjs/i18n/locales/locales/ja-JP.json +1 -0
  21. package/dist/cjs/i18n/locales/locales/ko-KR.json +1 -0
  22. package/dist/cjs/i18n/locales/locales/pt-PT.json +1 -0
  23. package/dist/cjs/i18n/locales/locales/zh-CN.json +1 -0
  24. package/dist/cjs/i18n/locales/pt-PT.json +1 -0
  25. package/dist/cjs/i18n/locales/zh-CN.json +1 -0
  26. package/dist/cjs/index.js +3 -1
  27. package/dist/cjs/mixins/OxyServices.accounts.js +10 -25
  28. package/dist/cjs/mixins/OxyServices.assets.js +76 -0
  29. package/dist/cjs/utils/cache.js +2 -0
  30. package/dist/cjs/utils/ssoBounce.js +59 -0
  31. package/dist/esm/.tsbuildinfo +1 -1
  32. package/dist/esm/AuthManager.js +27 -5
  33. package/dist/esm/OxyServices.base.js +71 -0
  34. package/dist/esm/i18n/locales/ar-SA.json +1 -0
  35. package/dist/esm/i18n/locales/ca-ES.json +1 -0
  36. package/dist/esm/i18n/locales/de-DE.json +1 -0
  37. package/dist/esm/i18n/locales/en-US.json +1 -0
  38. package/dist/esm/i18n/locales/es-ES.json +1 -0
  39. package/dist/esm/i18n/locales/fr-FR.json +1 -0
  40. package/dist/esm/i18n/locales/it-IT.json +1 -0
  41. package/dist/esm/i18n/locales/ja-JP.json +1 -0
  42. package/dist/esm/i18n/locales/ko-KR.json +1 -0
  43. package/dist/esm/i18n/locales/locales/ar-SA.json +1 -0
  44. package/dist/esm/i18n/locales/locales/ca-ES.json +1 -0
  45. package/dist/esm/i18n/locales/locales/de-DE.json +1 -0
  46. package/dist/esm/i18n/locales/locales/en-US.json +1 -0
  47. package/dist/esm/i18n/locales/locales/es-ES.json +1 -0
  48. package/dist/esm/i18n/locales/locales/fr-FR.json +1 -0
  49. package/dist/esm/i18n/locales/locales/it-IT.json +1 -0
  50. package/dist/esm/i18n/locales/locales/ja-JP.json +1 -0
  51. package/dist/esm/i18n/locales/locales/ko-KR.json +1 -0
  52. package/dist/esm/i18n/locales/locales/pt-PT.json +1 -0
  53. package/dist/esm/i18n/locales/locales/zh-CN.json +1 -0
  54. package/dist/esm/i18n/locales/pt-PT.json +1 -0
  55. package/dist/esm/i18n/locales/zh-CN.json +1 -0
  56. package/dist/esm/index.js +1 -1
  57. package/dist/esm/mixins/OxyServices.accounts.js +10 -25
  58. package/dist/esm/mixins/OxyServices.assets.js +76 -0
  59. package/dist/esm/utils/cache.js +2 -0
  60. package/dist/esm/utils/ssoBounce.js +57 -0
  61. package/dist/types/.tsbuildinfo +1 -1
  62. package/dist/types/OxyServices.base.d.ts +38 -0
  63. package/dist/types/index.d.ts +2 -2
  64. package/dist/types/mixins/OxyServices.accounts.d.ts +1 -0
  65. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
  66. package/dist/types/mixins/OxyServices.appData.d.ts +1 -0
  67. package/dist/types/mixins/OxyServices.assets.d.ts +36 -1
  68. package/dist/types/mixins/OxyServices.auth.d.ts +1 -0
  69. package/dist/types/mixins/OxyServices.civic.d.ts +1 -0
  70. package/dist/types/mixins/OxyServices.connectedApps.d.ts +1 -0
  71. package/dist/types/mixins/OxyServices.contacts.d.ts +1 -0
  72. package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
  73. package/dist/types/mixins/OxyServices.features.d.ts +1 -0
  74. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
  75. package/dist/types/mixins/OxyServices.identity.d.ts +1 -0
  76. package/dist/types/mixins/OxyServices.language.d.ts +1 -0
  77. package/dist/types/mixins/OxyServices.links.d.ts +1 -0
  78. package/dist/types/mixins/OxyServices.location.d.ts +1 -0
  79. package/dist/types/mixins/OxyServices.nodes.d.ts +1 -0
  80. package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
  81. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
  82. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
  83. package/dist/types/mixins/OxyServices.reputation.d.ts +1 -0
  84. package/dist/types/mixins/OxyServices.security.d.ts +1 -0
  85. package/dist/types/mixins/OxyServices.silent.d.ts +1 -0
  86. package/dist/types/mixins/OxyServices.sso.d.ts +1 -0
  87. package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
  88. package/dist/types/mixins/OxyServices.user.d.ts +1 -0
  89. package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
  90. package/dist/types/models/interfaces.d.ts +23 -0
  91. package/dist/types/utils/ssoBounce.d.ts +47 -0
  92. package/package.json +1 -1
  93. package/src/AuthManager.ts +29 -5
  94. package/src/OxyServices.base.ts +79 -0
  95. package/src/__tests__/establishDeviceRefreshSlot.test.ts +221 -0
  96. package/src/i18n/locales/ar-SA.json +1 -0
  97. package/src/i18n/locales/ca-ES.json +1 -0
  98. package/src/i18n/locales/de-DE.json +1 -0
  99. package/src/i18n/locales/en-US.json +1 -0
  100. package/src/i18n/locales/es-ES.json +1 -0
  101. package/src/i18n/locales/fr-FR.json +1 -0
  102. package/src/i18n/locales/it-IT.json +1 -0
  103. package/src/i18n/locales/ja-JP.json +1 -0
  104. package/src/i18n/locales/ko-KR.json +1 -0
  105. package/src/i18n/locales/pt-PT.json +1 -0
  106. package/src/i18n/locales/zh-CN.json +1 -0
  107. package/src/index.ts +3 -0
  108. package/src/mixins/OxyServices.accounts.ts +10 -33
  109. package/src/mixins/OxyServices.assets.ts +92 -1
  110. package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +135 -0
  111. package/src/models/interfaces.ts +24 -0
  112. package/src/utils/__tests__/ssoBounce.test.ts +41 -0
  113. package/src/utils/cache.ts +2 -0
  114. package/src/utils/ssoBounce.ts +61 -0
@@ -533,6 +533,30 @@ export interface ServiceAssetMetadata {
533
533
  status: 'active' | 'trash';
534
534
  }
535
535
 
536
+ /**
537
+ * Reverse-lookup asset metadata returned by `POST /assets/service/by-sha256`.
538
+ *
539
+ * Resolves a content-addressed `sha256` digest back to the live Oxy asset that
540
+ * holds those bytes: its file `id`, MIME type, byte `size`, storage `status`,
541
+ * and — for active, public, CDN-reachable assets only — a public `url`
542
+ * (`cloud.oxy.so`). This is the inverse of {@link ServiceAssetMetadata}: it lets
543
+ * a `files:read`-scoped service-to-server caller (e.g. Mention's MTN materializer
544
+ * / node-blob sync) turn a record's `blob.sha256` into a servable asset.
545
+ *
546
+ * `url` is omitted for private/unlisted assets (and for public assets whose
547
+ * bytes are not yet CDN-reachable) — those must be streamed through the origin.
548
+ * Unknown or deleted hashes are omitted from the response (never error the whole
549
+ * batch), so the result may be shorter than the requested hash list.
550
+ */
551
+ export interface ServiceAssetMetadataBySha {
552
+ sha256: string;
553
+ id: string;
554
+ mime: string;
555
+ size: number;
556
+ status: 'active' | 'trash';
557
+ url?: string;
558
+ }
559
+
536
560
  /**
537
561
  * Account storage usage (server-side usage, not local AsyncStorage)
538
562
  */
@@ -16,9 +16,11 @@ import {
16
16
  ssoNoSessionKey,
17
17
  ssoAttemptedKey,
18
18
  ssoPriorSessionKey,
19
+ ssoSignedOutKey,
19
20
  buildSsoBounceUrl,
20
21
  isCentralIdPOrigin,
21
22
  guardActive,
23
+ silentRestoreSuppressed,
22
24
  allowSsoBounce,
23
25
  } from '../ssoBounce';
24
26
  import { CENTRAL_AUTH_URL } from '../authWebUrl';
@@ -40,11 +42,50 @@ describe('per-origin key builders', () => {
40
42
  expect(ssoNoSessionKey(origin)).toBe('oxy_sso_no_session:https://mention.earth');
41
43
  expect(ssoAttemptedKey(origin)).toBe('oxy_sso_attempted:https://mention.earth');
42
44
  expect(ssoPriorSessionKey(origin)).toBe('oxy_sso_prior_session:https://mention.earth');
45
+ expect(ssoSignedOutKey(origin)).toBe('oxy_signed_out:https://mention.earth');
43
46
  });
44
47
 
45
48
  it('namespaces keys per origin so two RPs never collide', () => {
46
49
  expect(ssoStateKey('https://a.test')).not.toBe(ssoStateKey('https://b.test'));
47
50
  expect(ssoPriorSessionKey('https://a.test')).not.toBe(ssoPriorSessionKey('https://b.test'));
51
+ expect(ssoSignedOutKey('https://a.test')).not.toBe(ssoSignedOutKey('https://b.test'));
52
+ });
53
+ });
54
+
55
+ describe('silentRestoreSuppressed (deliberately-signed-out gate)', () => {
56
+ const origin = 'https://accounts.oxy.so';
57
+
58
+ function storageWith(value: string | null): Pick<Storage, 'getItem'> {
59
+ return { getItem: (key: string) => (key === ssoSignedOutKey(origin) ? value : null) };
60
+ }
61
+
62
+ it('is SUPPRESSED when the signed-out flag is set to "1" for this origin', () => {
63
+ expect(silentRestoreSuppressed(storageWith('1'), origin)).toBe(true);
64
+ });
65
+
66
+ it('is NOT suppressed when the flag is absent (normal restore)', () => {
67
+ expect(silentRestoreSuppressed(storageWith(null), origin)).toBe(false);
68
+ });
69
+
70
+ it('is NOT suppressed for any non-"1" value (only the exact set value gates)', () => {
71
+ expect(silentRestoreSuppressed(storageWith('0'), origin)).toBe(false);
72
+ expect(silentRestoreSuppressed(storageWith(''), origin)).toBe(false);
73
+ });
74
+
75
+ it('is per-origin: another origin\'s flag does not suppress this one', () => {
76
+ const otherOriginFlag: Pick<Storage, 'getItem'> = {
77
+ getItem: (key: string) => (key === ssoSignedOutKey('https://other.test') ? '1' : null),
78
+ };
79
+ expect(silentRestoreSuppressed(otherOriginFlag, origin)).toBe(false);
80
+ });
81
+
82
+ it('fails safe (NOT suppressed, never throws) when getItem throws', () => {
83
+ const throwing: Pick<Storage, 'getItem'> = {
84
+ getItem: () => {
85
+ throw new Error('storage locked');
86
+ },
87
+ };
88
+ expect(silentRestoreSuppressed(throwing, origin)).toBe(false);
48
89
  });
49
90
  });
50
91
 
@@ -240,6 +240,8 @@ export function registerCacheForCleanup(cache: TTLCache<any>): void {
240
240
  cache.cleanup();
241
241
  }
242
242
  }, 60000); // Every minute
243
+ // Never block process/worker exit on a housekeeping timer.
244
+ cleanupInterval.unref?.();
243
245
  }
244
246
  }
245
247
 
@@ -69,6 +69,7 @@ const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
69
69
  const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
70
70
  const CALLBACK_BOOTSTRAP_KEY_PREFIX = 'oxy_sso_callback_bootstrap:';
71
71
  const PRIOR_SESSION_KEY_PREFIX = 'oxy_sso_prior_session:';
72
+ const SIGNED_OUT_KEY_PREFIX = 'oxy_signed_out:';
72
73
 
73
74
  /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
74
75
  export function ssoStateKey(origin: string): string {
@@ -125,6 +126,37 @@ export function ssoPriorSessionKey(origin: string): string {
125
126
  return `${PRIOR_SESSION_KEY_PREFIX}${origin}`;
126
127
  }
127
128
 
129
+ /**
130
+ * Per-origin DURABLE "the user DELIBERATELY signed out on this device/origin"
131
+ * flag.
132
+ *
133
+ * Like {@link ssoPriorSessionKey} this lives in DURABLE storage (web
134
+ * `localStorage`; services uses its own `storageKeyPrefix`-scoped key), NOT the
135
+ * per-tab `sessionStorage` the loop-breaker keys use — it must survive a reload.
136
+ *
137
+ * It exists purely to suppress AUTOMATIC silent restore after a deliberate
138
+ * sign-out: a still-live IdP session (the central `fedcm_session` / the FedCM
139
+ * credential association) would otherwise let `fedcm-silent` / the per-apex
140
+ * `/auth/silent` iframe re-mint a session on the very next cold boot, so a user
141
+ * who pressed "Sign out" gets silently signed back in on reload. With this flag
142
+ * set, those silent cold-boot steps are skipped while the Gmail-style
143
+ * returning-account fast-path is otherwise preserved.
144
+ *
145
+ * Lifecycle (mirrors the existing gate machinery — set on a definitive event,
146
+ * cleared on its inverse):
147
+ * - SET on EXPLICIT full sign-out (alongside clearing the prior-session hint
148
+ * and the SSO bounce state).
149
+ * - CLEARED on ANY deliberate sign-in (password, FedCM, account switch, device
150
+ * claim) so a real sign-in fully re-enables silent restore — there is no
151
+ * "stuck signed out" state.
152
+ *
153
+ * NOTE: this gates only AUTOMATIC/silent restore. An INTERACTIVE sign-in always
154
+ * clears it first, so the user can always sign back in.
155
+ */
156
+ export function ssoSignedOutKey(origin: string): string {
157
+ return `${SIGNED_OUT_KEY_PREFIX}${origin}`;
158
+ }
159
+
128
160
  /**
129
161
  * Per-origin marker written by the pre-hydration callback bootstrap.
130
162
  *
@@ -267,6 +299,35 @@ export function guardActive(
267
299
  return now - ts < SSO_GUARD_TTL_MS;
268
300
  }
269
301
 
302
+ /**
303
+ * Whether AUTOMATIC silent restore is SUPPRESSED for this origin because the
304
+ * user deliberately signed out (the durable {@link ssoSignedOutKey} flag).
305
+ *
306
+ * When `true`, the silent cold-boot steps that can re-mint a session from a
307
+ * still-live IdP session WITHOUT user intent — `fedcm-silent` and the per-apex
308
+ * `/auth/silent` iframe — MUST be skipped, so a user who pressed "Sign out" is
309
+ * not silently signed back in on the next reload. Interactive sign-in clears the
310
+ * flag, so this never blocks a deliberate re-sign-in.
311
+ *
312
+ * Defensive: a `getItem` that throws (locked/disabled storage) is treated as NOT
313
+ * suppressed, so the gate fails toward the normal restore behaviour rather than
314
+ * wedging the user out.
315
+ *
316
+ * @param storage - The DURABLE storage to read (web `localStorage`; injected for
317
+ * testability).
318
+ * @param origin - The page origin whose signed-out flag to evaluate.
319
+ */
320
+ export function silentRestoreSuppressed(
321
+ storage: Pick<Storage, 'getItem'>,
322
+ origin: string,
323
+ ): boolean {
324
+ try {
325
+ return storage.getItem(ssoSignedOutKey(origin)) === '1';
326
+ } catch {
327
+ return false;
328
+ }
329
+ }
330
+
270
331
  /**
271
332
  * Inputs to the smart {@link allowSsoBounce} gate.
272
333
  */