@oxyhq/core 4.0.0 → 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 (119) 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/i18n/locales/en-US.json +391 -17
  8. package/dist/cjs/i18n/locales/es-ES.json +391 -17
  9. package/dist/cjs/i18n/locales/locales/en-US.json +391 -17
  10. package/dist/cjs/i18n/locales/locales/es-ES.json +391 -17
  11. package/dist/cjs/index.js +4 -8
  12. package/dist/cjs/mixins/OxyServices.accounts.js +54 -0
  13. package/dist/cjs/mixins/OxyServices.assets.js +2 -2
  14. package/dist/cjs/mixins/OxyServices.auth.js +3 -3
  15. package/dist/cjs/mixins/OxyServices.civic.js +3 -3
  16. package/dist/cjs/mixins/OxyServices.language.js +2 -2
  17. package/dist/cjs/mixins/OxyServices.utility.js +7 -95
  18. package/dist/cjs/utils/cacheKey.js +17 -19
  19. package/dist/cjs/utils/deviceManager.js +2 -2
  20. package/dist/cjs/utils/platform.js +0 -14
  21. package/dist/esm/.tsbuildinfo +1 -1
  22. package/dist/esm/HttpService.js +6 -18
  23. package/dist/esm/OxyServices.base.js +0 -21
  24. package/dist/esm/crypto/keyManager.js +4 -4
  25. package/dist/esm/crypto/polyfill.js +5 -4
  26. package/dist/esm/crypto/signatureService.js +39 -214
  27. package/dist/esm/i18n/locales/en-US.json +391 -17
  28. package/dist/esm/i18n/locales/es-ES.json +391 -17
  29. package/dist/esm/i18n/locales/locales/en-US.json +391 -17
  30. package/dist/esm/i18n/locales/locales/es-ES.json +391 -17
  31. package/dist/esm/index.js +1 -2
  32. package/dist/esm/mixins/OxyServices.accounts.js +54 -0
  33. package/dist/esm/mixins/OxyServices.assets.js +1 -1
  34. package/dist/esm/mixins/OxyServices.auth.js +1 -1
  35. package/dist/esm/mixins/OxyServices.civic.js +3 -3
  36. package/dist/esm/mixins/OxyServices.language.js +1 -1
  37. package/dist/esm/mixins/OxyServices.utility.js +6 -94
  38. package/dist/esm/utils/cacheKey.js +17 -19
  39. package/dist/esm/utils/deviceManager.js +1 -1
  40. package/dist/esm/utils/platform.js +0 -12
  41. package/dist/types/.tsbuildinfo +1 -1
  42. package/dist/types/HttpService.d.ts +3 -6
  43. package/dist/types/OxyServices.base.d.ts +0 -17
  44. package/dist/types/crypto/polyfill.d.ts +2 -2
  45. package/dist/types/crypto/signatureService.d.ts +18 -84
  46. package/dist/types/index.d.ts +3 -4
  47. package/dist/types/mixins/OxyServices.accounts.d.ts +57 -5
  48. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
  49. package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
  50. package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
  51. package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
  52. package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
  53. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
  54. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
  55. package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
  56. package/dist/types/mixins/OxyServices.features.d.ts +0 -2
  57. package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
  58. package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
  59. package/dist/types/mixins/OxyServices.language.d.ts +0 -2
  60. package/dist/types/mixins/OxyServices.links.d.ts +0 -2
  61. package/dist/types/mixins/OxyServices.location.d.ts +0 -2
  62. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
  63. package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
  64. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
  65. package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
  66. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
  67. package/dist/types/mixins/OxyServices.security.d.ts +0 -2
  68. package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
  69. package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
  70. package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
  71. package/dist/types/mixins/OxyServices.user.d.ts +0 -2
  72. package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
  73. package/dist/types/server/auth.d.ts +0 -6
  74. package/dist/types/server/index.d.ts +1 -1
  75. package/dist/types/utils/cacheKey.d.ts +6 -7
  76. package/dist/types/utils/platform.d.ts +0 -8
  77. package/package.json +4 -7
  78. package/src/HttpService.ts +6 -22
  79. package/src/OxyServices.base.ts +0 -23
  80. package/src/__tests__/httpServiceCache.test.ts +0 -19
  81. package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
  82. package/src/crypto/__tests__/keyManager.test.ts +9 -7
  83. package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
  84. package/src/crypto/__tests__/signedRecord.test.ts +37 -150
  85. package/src/crypto/keyManager.ts +28 -17
  86. package/src/crypto/polyfill.ts +5 -4
  87. package/src/crypto/signatureService.ts +67 -255
  88. package/src/i18n/locales/en-US.json +391 -17
  89. package/src/i18n/locales/es-ES.json +391 -17
  90. package/src/index.ts +3 -3
  91. package/src/mixins/OxyServices.accounts.ts +91 -3
  92. package/src/mixins/OxyServices.assets.ts +1 -1
  93. package/src/mixins/OxyServices.auth.ts +1 -1
  94. package/src/mixins/OxyServices.civic.ts +6 -17
  95. package/src/mixins/OxyServices.identity.ts +8 -2
  96. package/src/mixins/OxyServices.language.ts +1 -1
  97. package/src/mixins/OxyServices.nodes.ts +1 -12
  98. package/src/mixins/OxyServices.utility.ts +6 -119
  99. package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
  100. package/src/mixins/__tests__/accounts.test.ts +70 -0
  101. package/src/server/auth.ts +0 -7
  102. package/src/server/index.ts +0 -1
  103. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  104. package/src/utils/cacheKey.ts +16 -21
  105. package/src/utils/deviceManager.ts +1 -1
  106. package/src/utils/platform.ts +0 -14
  107. package/dist/cjs/crypto/canonicalJson.js +0 -107
  108. package/dist/cjs/utils/platformCrypto.js +0 -165
  109. package/dist/cjs/utils/platformCrypto.native.js +0 -123
  110. package/dist/esm/crypto/canonicalJson.js +0 -104
  111. package/dist/esm/utils/platformCrypto.js +0 -125
  112. package/dist/esm/utils/platformCrypto.native.js +0 -80
  113. package/dist/types/crypto/canonicalJson.d.ts +0 -44
  114. package/dist/types/utils/platformCrypto.d.ts +0 -87
  115. package/dist/types/utils/platformCrypto.native.d.ts +0 -54
  116. package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
  117. package/src/crypto/canonicalJson.ts +0 -120
  118. package/src/utils/platformCrypto.native.ts +0 -101
  119. package/src/utils/platformCrypto.ts +0 -145
@@ -95,8 +95,6 @@ export declare function OxyServicesSsoMixin<T extends typeof OxyServicesBase>(Ba
95
95
  hasValidToken(): boolean;
96
96
  getAccessToken(): string | null;
97
97
  getAccessTokenExpiry(): number | null;
98
- setActingAs(userId: string | null): void;
99
- getActingAs(): string | null;
100
98
  waitForAuth(timeoutMs?: number): Promise<boolean>;
101
99
  withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
102
100
  maxRetries?: number;
@@ -93,8 +93,6 @@ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>
93
93
  hasValidToken(): boolean;
94
94
  getAccessToken(): string | null;
95
95
  getAccessTokenExpiry(): number | null;
96
- setActingAs(userId: string | null): void;
97
- getActingAs(): string | null;
98
96
  waitForAuth(timeoutMs?: number): Promise<boolean>;
99
97
  withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
100
98
  maxRetries?: number;
@@ -364,8 +364,6 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
364
364
  hasValidToken(): boolean;
365
365
  getAccessToken(): string | null;
366
366
  getAccessTokenExpiry(): number | null;
367
- setActingAs(userId: string | null): void;
368
- getActingAs(): string | null;
369
367
  waitForAuth(timeoutMs?: number): Promise<boolean>;
370
368
  withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
371
369
  maxRetries?: number;
@@ -1,15 +1,5 @@
1
1
  import type { ApiError, User } from '../models/interfaces';
2
2
  import type { OxyServicesBase } from '../OxyServices.base';
3
- /**
4
- * Result from the account acting-as verification endpoint
5
- * (`GET /accounts/verify-acting-as`). Indicates whether a user is authorized to
6
- * act as a given account. The `account:act_as` capability is granted only to the
7
- * `owner`, `admin`, and `editor` account roles.
8
- */
9
- interface ActingAsVerification {
10
- authorized: boolean;
11
- role: 'owner' | 'admin' | 'editor';
12
- }
13
3
  /**
14
4
  * Result from the service-acting-as verification endpoint.
15
5
  * Confirms that a given service app holds an active delegation grant for
@@ -76,11 +66,6 @@ interface AuthMiddlewareOptions {
76
66
  }
77
67
  export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T): {
78
68
  new (...args: any[]): {
79
- /** @internal In-memory cache for acting-as verification results (TTL: 5 min) */
80
- _actingAsCache: Map<string, {
81
- result: ActingAsVerification | null;
82
- expiresAt: number;
83
- }>;
84
69
  /**
85
70
  * In-memory cache for service-acting-as verification.
86
71
  * Negative results are cached for 1min to avoid hammering the verify
@@ -92,14 +77,6 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
92
77
  result: ServiceActingAsVerification | null;
93
78
  expiresAt: number;
94
79
  }>;
95
- /**
96
- * Verify that a user is authorized to act as an account (direct membership
97
- * or inherited via an ancestor). Backed by `GET /accounts/verify-acting-as`.
98
- * Results are cached in-memory for 5 minutes to avoid repeated API calls.
99
- *
100
- * @internal Used by the auth() middleware — not part of the public API
101
- */
102
- verifyActingAs(userId: string, accountId: string): Promise<ActingAsVerification | null>;
103
80
  /**
104
81
  * Verify that a service app holds an active delegation grant authorising
105
82
  * it to act on behalf of `userId`. Returns the grant (with allowed scopes)
@@ -281,8 +258,6 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
281
258
  hasValidToken(): boolean;
282
259
  getAccessToken(): string | null;
283
260
  getAccessTokenExpiry(): number | null;
284
- setActingAs(userId: string | null): void;
285
- getActingAs(): string | null;
286
261
  waitForAuth(timeoutMs?: number): Promise<boolean>;
287
262
  withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
288
263
  maxRetries?: number;
@@ -313,13 +288,6 @@ interface AuthReq {
313
288
  userId: string;
314
289
  scopes: string[];
315
290
  };
316
- actingAs?: {
317
- userId: string;
318
- role: string;
319
- };
320
- originalUser?: {
321
- id: string;
322
- } & Partial<User>;
323
291
  }
324
292
  interface AuthRes {
325
293
  status(code: number): AuthRes;
@@ -8,10 +8,6 @@ export interface OxyRequestUser {
8
8
  avatar?: string;
9
9
  [key: string]: unknown;
10
10
  }
11
- export interface OxyActingAsContext {
12
- userId: string;
13
- role: 'owner' | 'admin' | 'editor';
14
- }
15
11
  export interface OxyServiceAppContext {
16
12
  appId: string;
17
13
  appName: string;
@@ -25,8 +21,6 @@ export interface OxyServiceActingAsContext {
25
21
  export interface OxyAuthRequest extends Request {
26
22
  userId?: string | null;
27
23
  user?: OxyRequestUser | null;
28
- originalUser?: OxyRequestUser | null;
29
- actingAs?: OxyActingAsContext;
30
24
  accessToken?: string;
31
25
  sessionId?: string | null;
32
26
  serviceApp?: OxyServiceAppContext;
@@ -15,7 +15,7 @@
15
15
  * ```
16
16
  */
17
17
  export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyUserId, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, } from './auth';
18
- export type { OxyActingAsContext, OxyAuthenticatedRequest, OxyAuthMiddlewareOptions, OxyAuthRequest, OxyRequestUser, OxyServiceActingAsContext, OxyServiceAppContext, } from './auth';
18
+ export type { OxyAuthenticatedRequest, OxyAuthMiddlewareOptions, OxyAuthRequest, OxyRequestUser, OxyServiceActingAsContext, OxyServiceAppContext, } from './auth';
19
19
  export { createOxyRateLimit } from './rateLimit';
20
20
  export type { OxyRateLimitOptions } from './rateLimit';
21
21
  export { assertSafePublicUrl, isBlockedIp, safeFetch, SsrfRejection, UpstreamError, ALLOWED_PORTS, ALLOWED_PROTOCOLS, BLOCKED_HOSTNAMES, DEFAULT_USER_AGENT, MAX_REDIRECTS, MAX_URL_LENGTH, UPSTREAM_HEADERS_TIMEOUT_MS, } from './safeFetch';
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Extracted from {@link HttpService} so the identity-tag derivation is a pure,
5
5
  * independently testable function with no dependency on instance/token state.
6
- * The HTTP service injects the live access token and acting-as id; everything
7
- * here is referentially transparent given those inputs.
6
+ * The HTTP service injects the live access token; everything here is
7
+ * referentially transparent given that input.
8
8
  */
9
9
  /**
10
10
  * Minimal JWT payload shape we read for cache scoping. The identity discriminator
@@ -57,11 +57,10 @@ export declare function fnv1a32(str: string): string;
57
57
  *
58
58
  * We use the decoded user id rather than the raw JWT so the token never lands
59
59
  * in a cache key (no token leakage through any cache-key logging, no key bloat).
60
- * The acting-as id is folded in because managed-account responses differ per
61
- * acting identity and `X-Acting-As` already changes the server response for
62
- * the same bearer token.
60
+ * Switching into a managed account mints a REAL new session whose access token
61
+ * carries the target account's id, so the identity tag changes naturally on a
62
+ * switch there is no separate acting-as discriminator to fold in.
63
63
  *
64
64
  * @param accessToken The current bearer access token, or `null` when anonymous.
65
- * @param actingAsUserId The active managed-account id, or `null`.
66
65
  */
67
- export declare function computeIdentityTag(accessToken: string | null, actingAsUserId: string | null): string;
66
+ export declare function computeIdentityTag(accessToken: string | null): string;
@@ -27,14 +27,6 @@ export declare function isIOS(): boolean;
27
27
  * Check if running on Android
28
28
  */
29
29
  export declare function isAndroid(): boolean;
30
- /**
31
- * Check if running in React Native
32
- */
33
- export declare function isReactNative(): boolean;
34
- /**
35
- * Check if running in Node.js
36
- */
37
- export declare function isNodeJS(): boolean;
38
30
  /**
39
31
  * Set the platform OS explicitly
40
32
  * Called by React Native entry point to register the platform
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -43,10 +43,6 @@
43
43
  },
44
44
  "./package.json": "./package.json"
45
45
  },
46
- "react-native": {
47
- "./dist/esm/utils/platformCrypto.js": "./dist/esm/utils/platformCrypto.native.js",
48
- "./dist/cjs/utils/platformCrypto.js": "./dist/cjs/utils/platformCrypto.native.js"
49
- },
50
46
  "files": [
51
47
  "dist",
52
48
  "src"
@@ -98,14 +94,15 @@
98
94
  }
99
95
  },
100
96
  "dependencies": {
101
- "@oxyhq/contracts": "^0.6.0",
97
+ "@oxyhq/contracts": "^0.7.0",
98
+ "@oxyhq/protocol": "^0.1.0",
102
99
  "bip39": "^3.1.0",
103
100
  "buffer": "^6.0.3",
104
101
  "elliptic": "^6.6.1",
105
102
  "invariant": "^2.2.4",
106
103
  "jwt-decode": "^4.0.0",
107
104
  "socket.io-client": "^4.8.1",
108
- "tldts": "^7.0.22",
105
+ "tldts": "^7.4.5",
109
106
  "zod": "^3.25.64"
110
107
  },
111
108
  "peerDependencies": {
@@ -18,7 +18,8 @@ import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/request
18
18
  import { retryAsync } from './utils/asyncUtils';
19
19
  import { handleHttpError } from './utils/errorUtils';
20
20
  import { jwtDecode } from 'jwt-decode';
21
- import { isNative, isReactNative, getPlatformOS } from './utils/platform';
21
+ import { isNative, getPlatformOS } from './utils/platform';
22
+ import { isReactNative } from '@oxyhq/protocol';
22
23
  import { computeIdentityTag, fnv1a32 } from './utils/cacheKey';
23
24
  import type { OxyConfig } from './models/interfaces';
24
25
 
@@ -238,9 +239,6 @@ export class HttpService {
238
239
  */
239
240
  private _tokenChangeListeners = new Set<(accessToken: string | null) => void>();
240
241
 
241
- // Acting-as identity for managed accounts
242
- private _actingAsUserId: string | null = null;
243
-
244
242
  // Performance monitoring
245
243
  private requestMetrics = {
246
244
  totalRequests: 0,
@@ -466,11 +464,6 @@ export class HttpService {
466
464
  });
467
465
  }
468
466
 
469
- // Add X-Acting-As header for managed account identity delegation
470
- if (this._actingAsUserId) {
471
- headers['X-Acting-As'] = this._actingAsUserId;
472
- }
473
-
474
467
  // Merge custom headers if provided
475
468
  if (config.headers) {
476
469
  Object.entries(config.headers).forEach(([key, value]) => {
@@ -820,12 +813,12 @@ export class HttpService {
820
813
  * Derive a stable, non-sensitive identity discriminator for cache scoping.
821
814
  *
822
815
  * Thin instance wrapper over the pure {@link computeIdentityTag} helper —
823
- * binds it to this instance's live access token and acting-as id. See that
824
- * function's docs for the full resolution contract (anon fallback, decoded
825
- * `userId || id`, token-hash fallback for undecodable tokens).
816
+ * binds it to this instance's live access token. See that function's docs for
817
+ * the full resolution contract (anon fallback, decoded `userId || id`,
818
+ * token-hash fallback for undecodable tokens).
826
819
  */
827
820
  private computeIdentityTag(): string {
828
- return computeIdentityTag(this.tokenStore.getAccessToken(), this._actingAsUserId);
821
+ return computeIdentityTag(this.tokenStore.getAccessToken());
829
822
  }
830
823
 
831
824
  /**
@@ -1107,15 +1100,6 @@ export class HttpService {
1107
1100
  return this.request<T>({ method: 'DELETE', url, ...config });
1108
1101
  }
1109
1102
 
1110
- // Acting-as identity management (managed accounts)
1111
- setActingAs(userId: string | null): void {
1112
- this._actingAsUserId = userId;
1113
- }
1114
-
1115
- getActingAs(): string | null {
1116
- return this._actingAsUserId;
1117
- }
1118
-
1119
1103
  // Token management
1120
1104
  setTokens(accessToken: string): void {
1121
1105
  this.tokenStore.setTokens(accessToken);
@@ -335,29 +335,6 @@ export class OxyServicesBase {
335
335
  }
336
336
  }
337
337
 
338
- /**
339
- * Set the acting-as identity for managed accounts.
340
- *
341
- * When set, all subsequent API requests will include the `X-Acting-As` header,
342
- * causing the server to attribute actions to the managed account. The
343
- * authenticated user must be an authorized manager of the target account.
344
- *
345
- * Pass `null` to clear and revert to the authenticated user's own identity.
346
- *
347
- * @param userId - The managed account user ID, or null to clear
348
- */
349
- public setActingAs(userId: string | null): void {
350
- this.httpService.setActingAs(userId);
351
- }
352
-
353
- /**
354
- * Get the current acting-as identity (managed account user ID), or null
355
- * if operating as the authenticated user's own identity.
356
- */
357
- public getActingAs(): string | null {
358
- return this.httpService.getActingAs();
359
- }
360
-
361
338
  /**
362
339
  * Wait for authentication to be ready
363
340
  *
@@ -112,25 +112,6 @@ describe('HttpService identity-scoped response cache', () => {
112
112
  expect(u2.profiles).toEqual(['for-user-2']);
113
113
  });
114
114
 
115
- it('partitions the cache by acting-as identity for the same bearer token', async () => {
116
- const http = newService();
117
- http.setTokens(makeJwt({ userId: 'owner-1' }));
118
-
119
- // Acting as managed account A.
120
- http.setActingAs('managed-a');
121
- fetchMock.mockResolvedValueOnce(jsonResponse({ items: ['a-only'] }));
122
- const a = await http.get<{ items: string[] }>('/some/managed-resource', { cache: true });
123
- expect(a.items).toEqual(['a-only']);
124
-
125
- // Acting as managed account B — different content, must miss the cache.
126
- http.setActingAs('managed-b');
127
- fetchMock.mockResolvedValueOnce(jsonResponse({ items: ['b-only'] }));
128
- const b = await http.get<{ items: string[] }>('/some/managed-resource', { cache: true });
129
-
130
- expect(fetchMock).toHaveBeenCalledTimes(2);
131
- expect(b.items).toEqual(['b-only']);
132
- });
133
-
134
115
  it('does NOT serve an authenticated cached response to an anonymous caller after clearTokens()', async () => {
135
116
  const http = newService();
136
117
  http.setTokens(makeJwt({ userId: 'user-1' }));
@@ -83,8 +83,9 @@ jest.mock(
83
83
  { virtual: true },
84
84
  );
85
85
 
86
- jest.mock('../../utils/platformCrypto', () => ({
86
+ jest.mock('@oxyhq/protocol', () => ({
87
87
  __esModule: true,
88
+ ...jest.requireActual('@oxyhq/protocol'),
88
89
  // eslint-disable-next-line @typescript-eslint/no-require-imports
89
90
  loadExpoCrypto: async () => require('expo-crypto'),
90
91
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -66,14 +66,16 @@ jest.mock(
66
66
  );
67
67
 
68
68
  // Production code routes platform-specific module loads through
69
- // `platformCrypto`, which ships in two physical variants on disk
70
- // (`platformCrypto.ts` / `platformCrypto.react-native.ts`) selected by the
71
- // consumer's bundler. Jest runs on Node — it picks the default variant,
72
- // which references Node's built-in `crypto`, not `expo-*`. For the test
73
- // suite to exercise the RN code paths, we mock the helper module to
74
- // delegate to the virtual `expo-*` modules registered above.
75
- jest.mock('../../utils/platformCrypto', () => ({
69
+ // `@oxyhq/protocol`'s `platform/crypto`, which ships in two physical variants
70
+ // on disk (`crypto.ts` / `crypto.native.ts`) selected by the consumer's
71
+ // bundler. Jest runs on Node — it picks the default variant, which references
72
+ // Node's built-in `crypto`, not `expo-*`. For the test suite to exercise the
73
+ // RN code paths, we override only the platform loaders to delegate to the
74
+ // virtual `expo-*` modules registered above, keeping every other protocol
75
+ // export (canonical bytes, signing, the platform predicates) real.
76
+ jest.mock('@oxyhq/protocol', () => ({
76
77
  __esModule: true,
78
+ ...jest.requireActual('@oxyhq/protocol'),
77
79
  loadExpoCrypto: async () => {
78
80
  // eslint-disable-next-line @typescript-eslint/no-require-imports
79
81
  return require('expo-crypto');
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { ec as EC } from 'elliptic';
12
+ import { verifySignature } from '@oxyhq/protocol';
12
13
  import { KeyManager } from '../keyManager';
13
14
  import { SignatureService } from '../signatureService';
14
15
 
@@ -40,7 +41,7 @@ describe('SignatureService.signChallengeWithSharedKey', () => {
40
41
  // uses, proving the format is unchanged and the shared key signed it.
41
42
  const message = `auth:${sharedPublicKey}:chal-123:${result.timestamp}`;
42
43
  await expect(
43
- SignatureService.verify(message, result.challenge, sharedPublicKey),
44
+ verifySignature(message, result.challenge, sharedPublicKey),
44
45
  ).resolves.toBe(true);
45
46
  });
46
47