@oxyhq/core 4.0.1 → 5.0.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 (109) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/HttpService.js +6 -18
  3. package/dist/cjs/OxyServices.base.js +0 -21
  4. package/dist/cjs/crypto/keyManager.js +7 -7
  5. package/dist/cjs/crypto/polyfill.js +6 -5
  6. package/dist/cjs/crypto/signatureService.js +44 -220
  7. package/dist/cjs/index.js +4 -8
  8. package/dist/cjs/mixins/OxyServices.accounts.js +54 -0
  9. package/dist/cjs/mixins/OxyServices.assets.js +2 -2
  10. package/dist/cjs/mixins/OxyServices.auth.js +3 -3
  11. package/dist/cjs/mixins/OxyServices.civic.js +3 -3
  12. package/dist/cjs/mixins/OxyServices.language.js +2 -2
  13. package/dist/cjs/mixins/OxyServices.utility.js +7 -95
  14. package/dist/cjs/utils/cacheKey.js +17 -19
  15. package/dist/cjs/utils/deviceManager.js +2 -2
  16. package/dist/cjs/utils/platform.js +0 -14
  17. package/dist/esm/.tsbuildinfo +1 -1
  18. package/dist/esm/HttpService.js +6 -18
  19. package/dist/esm/OxyServices.base.js +0 -21
  20. package/dist/esm/crypto/keyManager.js +4 -4
  21. package/dist/esm/crypto/polyfill.js +5 -4
  22. package/dist/esm/crypto/signatureService.js +39 -214
  23. package/dist/esm/index.js +1 -2
  24. package/dist/esm/mixins/OxyServices.accounts.js +54 -0
  25. package/dist/esm/mixins/OxyServices.assets.js +1 -1
  26. package/dist/esm/mixins/OxyServices.auth.js +1 -1
  27. package/dist/esm/mixins/OxyServices.civic.js +3 -3
  28. package/dist/esm/mixins/OxyServices.language.js +1 -1
  29. package/dist/esm/mixins/OxyServices.utility.js +6 -94
  30. package/dist/esm/utils/cacheKey.js +17 -19
  31. package/dist/esm/utils/deviceManager.js +1 -1
  32. package/dist/esm/utils/platform.js +0 -12
  33. package/dist/types/.tsbuildinfo +1 -1
  34. package/dist/types/HttpService.d.ts +3 -6
  35. package/dist/types/OxyServices.base.d.ts +0 -17
  36. package/dist/types/crypto/polyfill.d.ts +2 -2
  37. package/dist/types/crypto/signatureService.d.ts +18 -84
  38. package/dist/types/index.d.ts +3 -4
  39. package/dist/types/mixins/OxyServices.accounts.d.ts +57 -5
  40. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
  41. package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
  42. package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
  43. package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
  44. package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
  45. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
  46. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
  47. package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
  48. package/dist/types/mixins/OxyServices.features.d.ts +0 -2
  49. package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
  50. package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
  51. package/dist/types/mixins/OxyServices.language.d.ts +0 -2
  52. package/dist/types/mixins/OxyServices.links.d.ts +0 -2
  53. package/dist/types/mixins/OxyServices.location.d.ts +0 -2
  54. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
  55. package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
  56. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
  57. package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
  58. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
  59. package/dist/types/mixins/OxyServices.security.d.ts +0 -2
  60. package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
  61. package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
  62. package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
  63. package/dist/types/mixins/OxyServices.user.d.ts +0 -2
  64. package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
  65. package/dist/types/server/auth.d.ts +0 -6
  66. package/dist/types/server/index.d.ts +1 -1
  67. package/dist/types/utils/cacheKey.d.ts +6 -7
  68. package/dist/types/utils/platform.d.ts +0 -8
  69. package/package.json +4 -7
  70. package/src/HttpService.ts +6 -22
  71. package/src/OxyServices.base.ts +0 -23
  72. package/src/__tests__/httpServiceCache.test.ts +0 -19
  73. package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
  74. package/src/crypto/__tests__/keyManager.test.ts +9 -7
  75. package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
  76. package/src/crypto/__tests__/signedRecord.test.ts +37 -150
  77. package/src/crypto/keyManager.ts +28 -17
  78. package/src/crypto/polyfill.ts +5 -4
  79. package/src/crypto/signatureService.ts +67 -255
  80. package/src/index.ts +3 -3
  81. package/src/mixins/OxyServices.accounts.ts +91 -3
  82. package/src/mixins/OxyServices.assets.ts +1 -1
  83. package/src/mixins/OxyServices.auth.ts +1 -1
  84. package/src/mixins/OxyServices.civic.ts +6 -17
  85. package/src/mixins/OxyServices.identity.ts +8 -2
  86. package/src/mixins/OxyServices.language.ts +1 -1
  87. package/src/mixins/OxyServices.nodes.ts +1 -12
  88. package/src/mixins/OxyServices.utility.ts +6 -119
  89. package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
  90. package/src/mixins/__tests__/accounts.test.ts +70 -0
  91. package/src/server/auth.ts +0 -7
  92. package/src/server/index.ts +0 -1
  93. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  94. package/src/utils/cacheKey.ts +16 -21
  95. package/src/utils/deviceManager.ts +1 -1
  96. package/src/utils/platform.ts +0 -14
  97. package/dist/cjs/crypto/canonicalJson.js +0 -107
  98. package/dist/cjs/utils/platformCrypto.js +0 -165
  99. package/dist/cjs/utils/platformCrypto.native.js +0 -123
  100. package/dist/esm/crypto/canonicalJson.js +0 -104
  101. package/dist/esm/utils/platformCrypto.js +0 -125
  102. package/dist/esm/utils/platformCrypto.native.js +0 -80
  103. package/dist/types/crypto/canonicalJson.d.ts +0 -44
  104. package/dist/types/utils/platformCrypto.d.ts +0 -87
  105. package/dist/types/utils/platformCrypto.native.d.ts +0 -54
  106. package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
  107. package/src/crypto/canonicalJson.ts +0 -120
  108. package/src/utils/platformCrypto.native.ts +0 -101
  109. package/src/utils/platformCrypto.ts +0 -145
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OxyServicesAccountsMixin = OxyServicesAccountsMixin;
4
+ const userIdentity_1 = require("../utils/userIdentity");
4
5
  const mixinHelpers_1 = require("./mixinHelpers");
5
6
  function OxyServicesAccountsMixin(Base) {
6
7
  return class extends Base {
@@ -43,6 +44,59 @@ function OxyServicesAccountsMixin(Base) {
43
44
  throw this.handleError(error);
44
45
  }
45
46
  }
47
+ /**
48
+ * Switch the active session INTO a managed account.
49
+ *
50
+ * Calls `POST /accounts/:id/switch` with the signed-in operator's bearer.
51
+ * The server authorises the operator (must hold `account:act_as` over the
52
+ * target, directly or inherited — else 403; 404 if missing/archived; 403 if
53
+ * the target is a personal account), then mints a REAL session for the
54
+ * target account and returns it in the canonical login / `claimSessionByToken`
55
+ * shape (`{ sessionId, deviceId, expiresAt, accessToken, user, authuser }`).
56
+ *
57
+ * Unlike the removed `X-Acting-As` delegation header, the returned session
58
+ * IS the new identity: this plants `accessToken` as the active token —
59
+ * exactly like `claimSessionByToken` / `verifyChallenge` — so every
60
+ * subsequent request authenticates as the target account. The refresh token
61
+ * is the server-set httpOnly `oxy_rt_<authuser>` cookie (never in the body),
62
+ * so it joins the device multi-account set and survives reload /
63
+ * `refresh-all` with no extra client work.
64
+ *
65
+ * After planting, the SDK's identity-scoped GET cache is fully cleared so
66
+ * every cached read re-fetches as the new account. (The consuming
67
+ * `OxyContext` additionally invalidates its React Query cache and updates
68
+ * session state from the returned `user`; this clears the SDK's own HTTP
69
+ * cache at the source — `setTokens` deliberately preserves the warm cache for
70
+ * same-user silent refreshes, so the sweep here is explicit.)
71
+ *
72
+ * @param accountId - The target account's Mongo `_id`.
73
+ * @returns The minted session (planted) plus the device `authuser` slot.
74
+ */
75
+ async switchToAccount(accountId) {
76
+ try {
77
+ const res = await this.makeRequest('POST', `/accounts/${encodeURIComponent(accountId)}/switch`, undefined, { cache: false });
78
+ // Plant the freshly minted session as the ACTIVE session, mirroring
79
+ // `claimSessionByToken` / `verifyChallenge`: the response body carries
80
+ // the first access token; the refresh token is the server-set httpOnly
81
+ // cookie, so there is nothing else to store here.
82
+ if (res?.accessToken) {
83
+ this.setTokens(res.accessToken);
84
+ }
85
+ // Identity changed → drop the entire GET response cache so no entry
86
+ // personalised for the previous identity is reused. Cache keys are
87
+ // identity-scoped, so a different identity could not READ the old
88
+ // entries anyway, but clearing guarantees a clean refetch as the new
89
+ // account and frees the prior identity's resident data.
90
+ this.clearCache();
91
+ return {
92
+ ...res,
93
+ user: (0, userIdentity_1.normalizeUserIdentity)(res.user),
94
+ };
95
+ }
96
+ catch (error) {
97
+ throw this.handleError(error);
98
+ }
99
+ }
46
100
  /**
47
101
  * Create a new (non-personal) account. The caller becomes its `owner`.
48
102
  * @param data - Account configuration: kind, optional parent, and profile.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OxyServicesAssetsMixin = OxyServicesAssetsMixin;
4
- const platform_1 = require("../utils/platform");
4
+ const protocol_1 = require("@oxyhq/protocol");
5
5
  function OxyServicesAssetsMixin(Base) {
6
6
  return class extends Base {
7
7
  constructor(...args) {
@@ -173,7 +173,7 @@ function OxyServicesAssetsMixin(Base) {
173
173
  }
174
174
  else if ('uri' in file && typeof file.uri === 'string') {
175
175
  const descriptor = file;
176
- if ((0, platform_1.isReactNative)()) {
176
+ if ((0, protocol_1.isReactNative)()) {
177
177
  // React Native file descriptor — RN's FormData handles {uri, type, name} natively.
178
178
  // It reads the file from disk during the multipart request — no in-JS Blob
179
179
  // conversion (which would fail on Hermes for ArrayBuffer-backed Blobs).
@@ -5,7 +5,7 @@ exports.OxyServicesAuthMixin = OxyServicesAuthMixin;
5
5
  const OxyServices_errors_1 = require("../OxyServices.errors");
6
6
  const keyManager_1 = require("../crypto/keyManager");
7
7
  const signatureService_1 = require("../crypto/signatureService");
8
- const platformCrypto_1 = require("../utils/platformCrypto");
8
+ const protocol_1 = require("@oxyhq/protocol");
9
9
  const loggerUtils_1 = require("../utils/loggerUtils");
10
10
  const userIdentity_1 = require("../utils/userIdentity");
11
11
  /**
@@ -61,7 +61,7 @@ function OxyServicesAuthMixin(Base) {
61
61
  * @internal
62
62
  */
63
63
  async _hashApiKey(apiKey) {
64
- const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
64
+ const nodeCrypto = await (0, protocol_1.loadNodeCrypto)();
65
65
  return nodeCrypto.createHash('sha256').update(apiKey).digest('hex');
66
66
  }
67
67
  /**
@@ -113,7 +113,7 @@ function OxyServicesAuthMixin(Base) {
113
113
  // Verify the secret on every cache hit, regardless of token freshness.
114
114
  // Constant-time compare prevents timing oracles on the stored secret.
115
115
  if (entry) {
116
- const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
116
+ const nodeCrypto = await (0, protocol_1.loadNodeCrypto)();
117
117
  const storedSecretBuf = entry.secretBuf;
118
118
  const lengthMatch = storedSecretBuf.length === providedSecretBuf.length;
119
119
  // Always run timingSafeEqual on equal-length inputs to keep timing flat.
@@ -4,7 +4,7 @@ exports.parseIdPayload = parseIdPayload;
4
4
  exports.parseAttestPayload = parseAttestPayload;
5
5
  exports.verifyPublicCardAttestation = verifyPublicCardAttestation;
6
6
  exports.OxyServicesCivicMixin = OxyServicesCivicMixin;
7
- const canonicalJson_1 = require("../crypto/canonicalJson");
7
+ const protocol_1 = require("@oxyhq/protocol");
8
8
  const signatureService_1 = require("../crypto/signatureService");
9
9
  const OxyServices_identity_1 = require("./OxyServices.identity");
10
10
  const mixinHelpers_1 = require("./mixinHelpers");
@@ -156,7 +156,7 @@ function parseAttestPayload(raw) {
156
156
  * key, matching the server which omits absent keys entirely) and checks the
157
157
  * `ES256K-DER-SHA256` signature against `attestation.publicKey`.
158
158
  *
159
- * NEVER throws: `SignatureService.verify` already swallows malformed-input
159
+ * NEVER throws: `verifySignature` already swallows malformed-input
160
160
  * errors and returns `false`, and an absent attestation short-circuits to
161
161
  * `false`. A pure, reusable helper (Commons can call it on a cached card).
162
162
  *
@@ -171,7 +171,7 @@ async function verifyPublicCardAttestation(card, attestation) {
171
171
  if (!signature || !publicKey) {
172
172
  return false;
173
173
  }
174
- return signatureService_1.SignatureService.verify((0, canonicalJson_1.canonicalize)(card), signature, publicKey);
174
+ return (0, protocol_1.verifySignature)((0, protocol_1.canonicalize)(card), signature, publicKey);
175
175
  }
176
176
  function OxyServicesCivicMixin(Base) {
177
177
  return class extends Base {
@@ -5,7 +5,7 @@ exports.OxyServicesLanguageMixin = OxyServicesLanguageMixin;
5
5
  * Language Methods Mixin
6
6
  */
7
7
  const languageUtils_1 = require("../utils/languageUtils");
8
- const platformCrypto_1 = require("../utils/platformCrypto");
8
+ const protocol_1 = require("@oxyhq/protocol");
9
9
  const debugUtils_1 = require("../shared/utils/debugUtils");
10
10
  function OxyServicesLanguageMixin(Base) {
11
11
  return class extends Base {
@@ -22,7 +22,7 @@ function OxyServicesLanguageMixin(Base) {
22
22
  // `loadAsyncStorage` is per-platform: the RN variant statically imports
23
23
  // @react-native-async-storage/async-storage, the default variant throws
24
24
  // (never called outside RN because of the `isReactNative` gate above).
25
- const asyncStorageModule = await (0, platformCrypto_1.loadAsyncStorage)();
25
+ const asyncStorageModule = await (0, protocol_1.loadAsyncStorage)();
26
26
  const storage = asyncStorageModule.default;
27
27
  return {
28
28
  getItem: storage.getItem.bind(storage),
@@ -8,7 +8,7 @@ exports.OxyServicesUtilityMixin = OxyServicesUtilityMixin;
8
8
  * and Express.js authentication middleware
9
9
  */
10
10
  const jwt_decode_1 = require("jwt-decode");
11
- const platformCrypto_1 = require("../utils/platformCrypto");
11
+ const protocol_1 = require("@oxyhq/protocol");
12
12
  const loggerUtils_1 = require("../utils/loggerUtils");
13
13
  const mixinHelpers_1 = require("./mixinHelpers");
14
14
  /**
@@ -50,8 +50,6 @@ function OxyServicesUtilityMixin(Base) {
50
50
  // monorepo-wide refactor of the mixin pipeline.
51
51
  constructor(...args) {
52
52
  super(...args);
53
- /** @internal In-memory cache for acting-as verification results (TTL: 5 min) */
54
- this._actingAsCache = new Map();
55
53
  /**
56
54
  * In-memory cache for service-acting-as verification.
57
55
  * Negative results are cached for 1min to avoid hammering the verify
@@ -61,46 +59,6 @@ function OxyServicesUtilityMixin(Base) {
61
59
  */
62
60
  this._serviceActingAsCache = new Map();
63
61
  }
64
- /**
65
- * Verify that a user is authorized to act as an account (direct membership
66
- * or inherited via an ancestor). Backed by `GET /accounts/verify-acting-as`.
67
- * Results are cached in-memory for 5 minutes to avoid repeated API calls.
68
- *
69
- * @internal Used by the auth() middleware — not part of the public API
70
- */
71
- async verifyActingAs(userId, accountId) {
72
- const cacheKey = `${userId}:${accountId}`;
73
- const now = Date.now();
74
- // Check cache
75
- const cached = this._actingAsCache.get(cacheKey);
76
- if (cached && cached.expiresAt > now) {
77
- return cached.result;
78
- }
79
- // Query the API
80
- try {
81
- const result = await this.makeRequest('GET', '/accounts/verify-acting-as', { accountId, userId }, { cache: false, retry: false, timeout: 5000 });
82
- // Cache successful result for 5 minutes
83
- this._actingAsCache.set(cacheKey, {
84
- result: result && result.authorized ? result : null,
85
- expiresAt: now + 5 * 60 * 1000,
86
- });
87
- return result && result.authorized ? result : null;
88
- }
89
- catch (error) {
90
- loggerUtils_1.logger.warn('[oxy.auth] verifyActingAs lookup failed — caching negative result', {
91
- component: 'auth',
92
- method: 'verifyActingAs',
93
- userId,
94
- accountId,
95
- }, error);
96
- // Cache negative result for 1 minute to avoid hammering on transient errors
97
- this._actingAsCache.set(cacheKey, {
98
- result: null,
99
- expiresAt: now + 1 * 60 * 1000,
100
- });
101
- return null;
102
- }
103
- }
104
62
  /**
105
63
  * Verify that a service app holds an active delegation grant authorising
106
64
  * it to act on behalf of `userId`. Returns the grant (with allowed scopes)
@@ -221,47 +179,6 @@ function OxyServicesUtilityMixin(Base) {
221
179
  const oxyInstance = this;
222
180
  // Return an async middleware function
223
181
  return async (req, res, next) => {
224
- // Process X-Acting-As header for managed account identity delegation.
225
- // Called after successful authentication, before next(). If the header
226
- // is present, verifies authorization and swaps the request identity to
227
- // the managed account, preserving the original user for audit trails.
228
- const processActingAs = async () => {
229
- const actingAsUserId = req.headers['x-acting-as'];
230
- if (!actingAsUserId || typeof actingAsUserId !== 'string')
231
- return true; // No header, proceed normally
232
- const currentUserId = req.userId;
233
- if (!currentUserId)
234
- return true; // No authenticated user yet — nothing to swap
235
- const verification = await oxyInstance.verifyActingAs(currentUserId, actingAsUserId);
236
- if (!verification) {
237
- const error = {
238
- error: 'ACTING_AS_UNAUTHORIZED',
239
- message: 'Not authorized to act as this account',
240
- code: 'ACTING_AS_UNAUTHORIZED',
241
- status: 403,
242
- };
243
- if (onError) {
244
- onError(error);
245
- }
246
- else {
247
- res.status(403).json(error);
248
- }
249
- return false;
250
- }
251
- // Preserve original user for audit trails
252
- req.originalUser = { id: currentUserId, ...(req.user ?? {}) };
253
- req.actingAs = { userId: actingAsUserId, role: verification.role };
254
- // Swap user identity to the managed account
255
- req.userId = actingAsUserId;
256
- req.user = { id: actingAsUserId, _id: actingAsUserId };
257
- if (debug) {
258
- loggerUtils_1.logger.debug(`[oxy.auth] Acting as ${actingAsUserId} (role=${verification.role}) original=${currentUserId}`, {
259
- component: 'auth',
260
- method: 'auth.processActingAs',
261
- });
262
- }
263
- return true;
264
- };
265
182
  try {
266
183
  // Extract token from Authorization header.
267
184
  // Node/Express normalizes `Authorization` to a string; we guard
@@ -343,9 +260,9 @@ function OxyServicesUtilityMixin(Base) {
343
260
  // Signature verification uses a manual HMAC-SHA256 compare because
344
261
  // this file ships into RN/web bundles where `jsonwebtoken` is
345
262
  // unavailable. The middleware only ever runs on Node hosts (see
346
- // platformCrypto's doc-comment), and `loadNodeCrypto` is per-
347
- // platform: the RN variant throws so Metro never bundles a Node
348
- // built-in reference.
263
+ // `@oxyhq/protocol`'s `platform/crypto` doc-comment), and
264
+ // `loadNodeCrypto` is per-platform: the RN variant throws so Metro
265
+ // never bundles a Node built-in reference.
349
266
  try {
350
267
  await verifyServiceTokenSignature(token, jwtSecret);
351
268
  verifyServiceTokenClaims(decoded, {
@@ -554,10 +471,7 @@ function OxyServicesUtilityMixin(Base) {
554
471
  method: 'auth',
555
472
  });
556
473
  }
557
- // Process X-Acting-As header before proceeding
558
- if (await processActingAs())
559
- return next();
560
- return;
474
+ return next();
561
475
  }
562
476
  catch (validationError) {
563
477
  if (debug) {
@@ -621,9 +535,7 @@ function OxyServicesUtilityMixin(Base) {
621
535
  method: 'auth',
622
536
  });
623
537
  }
624
- // Process X-Acting-As header before proceeding
625
- if (await processActingAs())
626
- next();
538
+ next();
627
539
  }
628
540
  catch (error) {
629
541
  const handled = oxyInstance.handleError(error);
@@ -861,7 +773,7 @@ function OxyServicesUtilityMixin(Base) {
861
773
  * `ServiceTokenSignatureError` on signature mismatch — both map to 401.
862
774
  */
863
775
  async function verifyServiceTokenSignature(token, secret) {
864
- const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
776
+ const nodeCrypto = await (0, protocol_1.loadNodeCrypto)();
865
777
  const { createHmac, timingSafeEqual } = nodeCrypto;
866
778
  const parts = token.split('.');
867
779
  if (parts.length !== 3) {
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * Extracted from {@link HttpService} so the identity-tag derivation is a pure,
6
6
  * independently testable function with no dependency on instance/token state.
7
- * The HTTP service injects the live access token and acting-as id; everything
8
- * here is referentially transparent given those inputs.
7
+ * The HTTP service injects the live access token; everything here is
8
+ * referentially transparent given that input.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.ANON_IDENTITY = void 0;
@@ -63,25 +63,23 @@ function fnv1a32(str) {
63
63
  *
64
64
  * We use the decoded user id rather than the raw JWT so the token never lands
65
65
  * in a cache key (no token leakage through any cache-key logging, no key bloat).
66
- * The acting-as id is folded in because managed-account responses differ per
67
- * acting identity and `X-Acting-As` already changes the server response for
68
- * the same bearer token.
66
+ * Switching into a managed account mints a REAL new session whose access token
67
+ * carries the target account's id, so the identity tag changes naturally on a
68
+ * switch there is no separate acting-as discriminator to fold in.
69
69
  *
70
70
  * @param accessToken The current bearer access token, or `null` when anonymous.
71
- * @param actingAsUserId The active managed-account id, or `null`.
72
71
  */
73
- function computeIdentityTag(accessToken, actingAsUserId) {
74
- let principal = exports.ANON_IDENTITY;
75
- if (accessToken) {
76
- try {
77
- const decoded = (0, jwt_decode_1.jwtDecode)(accessToken);
78
- principal = decoded.userId || decoded.id || `t${fnv1a32(accessToken)}`;
79
- }
80
- catch {
81
- // Undecodable token — still partition it away from anon and from other
82
- // tokens via a hash. Never silently fall back to ANON_IDENTITY.
83
- principal = `t${fnv1a32(accessToken)}`;
84
- }
72
+ function computeIdentityTag(accessToken) {
73
+ if (!accessToken) {
74
+ return exports.ANON_IDENTITY;
75
+ }
76
+ try {
77
+ const decoded = (0, jwt_decode_1.jwtDecode)(accessToken);
78
+ return decoded.userId || decoded.id || `t${fnv1a32(accessToken)}`;
79
+ }
80
+ catch {
81
+ // Undecodable token still partition it away from anon and from other
82
+ // tokens via a hash. Never silently fall back to ANON_IDENTITY.
83
+ return `t${fnv1a32(accessToken)}`;
85
84
  }
86
- return actingAsUserId ? `${principal}~as${actingAsUserId}` : principal;
87
85
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DeviceManager = void 0;
4
- const platformCrypto_1 = require("./platformCrypto");
4
+ const protocol_1 = require("@oxyhq/protocol");
5
5
  /**
6
6
  * Client-side device management utility
7
7
  * Handles persistent device identification across app sessions
@@ -22,7 +22,7 @@ class DeviceManager {
22
22
  // `loadAsyncStorage` is per-platform: the RN variant statically imports
23
23
  // @react-native-async-storage/async-storage, the default variant throws
24
24
  // (never called outside RN because of the `isReactNative()` gate above).
25
- const asyncStorageModule = await (0, platformCrypto_1.loadAsyncStorage)();
25
+ const asyncStorageModule = await (0, protocol_1.loadAsyncStorage)();
26
26
  const storage = asyncStorageModule.default;
27
27
  return {
28
28
  getItem: storage.getItem.bind(storage),
@@ -12,8 +12,6 @@ exports.isWeb = isWeb;
12
12
  exports.isNative = isNative;
13
13
  exports.isIOS = isIOS;
14
14
  exports.isAndroid = isAndroid;
15
- exports.isReactNative = isReactNative;
16
- exports.isNodeJS = isNodeJS;
17
15
  exports.setPlatformOS = setPlatformOS;
18
16
  /**
19
17
  * Detect the current platform without importing react-native
@@ -84,18 +82,6 @@ function isIOS() {
84
82
  function isAndroid() {
85
83
  return getPlatformOS() === 'android';
86
84
  }
87
- /**
88
- * Check if running in React Native
89
- */
90
- function isReactNative() {
91
- return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
92
- }
93
- /**
94
- * Check if running in Node.js
95
- */
96
- function isNodeJS() {
97
- return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
98
- }
99
85
  /**
100
86
  * Set the platform OS explicitly
101
87
  * Called by React Native entry point to register the platform