@oxyhq/core 1.0.2 → 1.2.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 (122) hide show
  1. package/dist/cjs/AuthManager.js +35 -10
  2. package/dist/cjs/CrossDomainAuth.js +2 -2
  3. package/dist/cjs/HttpService.js +40 -24
  4. package/dist/cjs/OxyServices.base.js +16 -3
  5. package/dist/cjs/crypto/keyManager.js +29 -24
  6. package/dist/cjs/crypto/polyfill.js +6 -1
  7. package/dist/cjs/crypto/signatureService.js +40 -31
  8. package/dist/cjs/i18n/index.js +36 -45
  9. package/dist/cjs/i18n/locales/ar-SA.json +114 -115
  10. package/dist/cjs/i18n/locales/ca-ES.json +114 -115
  11. package/dist/cjs/i18n/locales/de-DE.json +114 -115
  12. package/dist/cjs/i18n/locales/en-US.json +936 -936
  13. package/dist/cjs/i18n/locales/es-ES.json +924 -924
  14. package/dist/cjs/i18n/locales/fr-FR.json +114 -115
  15. package/dist/cjs/i18n/locales/it-IT.json +114 -115
  16. package/dist/cjs/i18n/locales/ja-JP.json +1 -1
  17. package/dist/cjs/i18n/locales/ko-KR.json +114 -115
  18. package/dist/cjs/i18n/locales/locales/ar-SA.json +120 -0
  19. package/dist/cjs/i18n/locales/locales/ca-ES.json +120 -0
  20. package/dist/cjs/i18n/locales/locales/de-DE.json +120 -0
  21. package/dist/cjs/i18n/locales/locales/en-US.json +956 -0
  22. package/dist/cjs/i18n/locales/locales/es-ES.json +944 -0
  23. package/dist/cjs/i18n/locales/locales/fr-FR.json +120 -0
  24. package/dist/cjs/i18n/locales/locales/it-IT.json +120 -0
  25. package/dist/cjs/i18n/locales/locales/ja-JP.json +119 -0
  26. package/dist/cjs/i18n/locales/locales/ko-KR.json +120 -0
  27. package/dist/cjs/i18n/locales/locales/pt-PT.json +120 -0
  28. package/dist/cjs/i18n/locales/locales/zh-CN.json +120 -0
  29. package/dist/cjs/i18n/locales/pt-PT.json +114 -115
  30. package/dist/cjs/i18n/locales/zh-CN.json +114 -115
  31. package/dist/cjs/mixins/OxyServices.fedcm.js +21 -45
  32. package/dist/cjs/mixins/OxyServices.language.js +5 -2
  33. package/dist/cjs/mixins/OxyServices.popup.js +16 -6
  34. package/dist/cjs/mixins/OxyServices.privacy.js +2 -1
  35. package/dist/cjs/mixins/OxyServices.redirect.js +16 -6
  36. package/dist/cjs/mixins/OxyServices.security.js +3 -2
  37. package/dist/cjs/shared/utils/debugUtils.js +8 -1
  38. package/dist/cjs/utils/deviceManager.js +4 -6
  39. package/dist/cjs/utils/platform.js +3 -2
  40. package/dist/esm/AuthManager.js +35 -10
  41. package/dist/esm/CrossDomainAuth.js +2 -2
  42. package/dist/esm/HttpService.js +40 -24
  43. package/dist/esm/OxyServices.base.js +16 -3
  44. package/dist/esm/crypto/keyManager.js +29 -24
  45. package/dist/esm/crypto/polyfill.js +6 -1
  46. package/dist/esm/crypto/signatureService.js +40 -31
  47. package/dist/esm/i18n/index.js +11 -23
  48. package/dist/esm/i18n/locales/ar-SA.json +114 -115
  49. package/dist/esm/i18n/locales/ca-ES.json +114 -115
  50. package/dist/esm/i18n/locales/de-DE.json +114 -115
  51. package/dist/esm/i18n/locales/en-US.json +936 -936
  52. package/dist/esm/i18n/locales/es-ES.json +924 -924
  53. package/dist/esm/i18n/locales/fr-FR.json +114 -115
  54. package/dist/esm/i18n/locales/it-IT.json +114 -115
  55. package/dist/esm/i18n/locales/ja-JP.json +1 -1
  56. package/dist/esm/i18n/locales/ko-KR.json +114 -115
  57. package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
  58. package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
  59. package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
  60. package/dist/esm/i18n/locales/locales/en-US.json +956 -0
  61. package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
  62. package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
  63. package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
  64. package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
  65. package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
  66. package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
  67. package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
  68. package/dist/esm/i18n/locales/pt-PT.json +114 -115
  69. package/dist/esm/i18n/locales/zh-CN.json +114 -115
  70. package/dist/esm/mixins/OxyServices.fedcm.js +21 -45
  71. package/dist/esm/mixins/OxyServices.language.js +5 -2
  72. package/dist/esm/mixins/OxyServices.popup.js +16 -6
  73. package/dist/esm/mixins/OxyServices.privacy.js +2 -1
  74. package/dist/esm/mixins/OxyServices.redirect.js +16 -6
  75. package/dist/esm/mixins/OxyServices.security.js +3 -2
  76. package/dist/esm/shared/utils/debugUtils.js +8 -1
  77. package/dist/esm/utils/deviceManager.js +4 -6
  78. package/dist/esm/utils/platform.js +3 -2
  79. package/dist/types/AuthManager.d.ts +4 -1
  80. package/dist/types/CrossDomainAuth.d.ts +2 -2
  81. package/dist/types/HttpService.d.ts +2 -0
  82. package/dist/types/OxyServices.base.d.ts +4 -1
  83. package/dist/types/OxyServices.d.ts +13 -0
  84. package/dist/types/index.d.ts +3 -0
  85. package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
  86. package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
  87. package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
  88. package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
  89. package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
  90. package/dist/types/mixins/OxyServices.features.d.ts +2 -0
  91. package/dist/types/mixins/OxyServices.fedcm.d.ts +4 -2
  92. package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
  93. package/dist/types/mixins/OxyServices.language.d.ts +2 -0
  94. package/dist/types/mixins/OxyServices.location.d.ts +2 -0
  95. package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
  96. package/dist/types/mixins/OxyServices.popup.d.ts +2 -0
  97. package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
  98. package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
  99. package/dist/types/mixins/OxyServices.security.d.ts +2 -0
  100. package/dist/types/mixins/OxyServices.user.d.ts +2 -0
  101. package/dist/types/mixins/OxyServices.utility.d.ts +2 -0
  102. package/package.json +1 -2
  103. package/src/AuthManager.ts +42 -16
  104. package/src/CrossDomainAuth.ts +2 -2
  105. package/src/HttpService.ts +40 -26
  106. package/src/OxyServices.base.ts +21 -4
  107. package/src/OxyServices.ts +23 -2
  108. package/src/crypto/keyManager.ts +30 -25
  109. package/src/crypto/polyfill.ts +6 -1
  110. package/src/crypto/signatureService.ts +43 -37
  111. package/src/i18n/index.ts +33 -45
  112. package/src/index.ts +3 -0
  113. package/src/mixins/OxyServices.fedcm.ts +22 -48
  114. package/src/mixins/OxyServices.language.ts +6 -3
  115. package/src/mixins/OxyServices.popup.ts +16 -6
  116. package/src/mixins/OxyServices.privacy.ts +2 -1
  117. package/src/mixins/OxyServices.redirect.ts +16 -6
  118. package/src/mixins/OxyServices.security.ts +3 -2
  119. package/src/shared/utils/__tests__/debugUtils.test.ts +55 -0
  120. package/src/shared/utils/debugUtils.ts +6 -1
  121. package/src/utils/deviceManager.ts +5 -6
  122. package/src/utils/platform.ts +3 -2
@@ -10,6 +10,7 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.AuthManager = void 0;
12
12
  exports.createAuthManager = createAuthManager;
13
+ const asyncUtils_1 = require("./utils/asyncUtils");
13
14
  /**
14
15
  * Storage keys used by AuthManager.
15
16
  */
@@ -102,6 +103,7 @@ class AuthManager {
102
103
  this.listeners = new Set();
103
104
  this.currentUser = null;
104
105
  this.refreshTimer = null;
106
+ this.refreshPromise = null;
105
107
  this.oxyServices = oxyServices;
106
108
  this.config = {
107
109
  storage: config.storage ?? this.getDefaultStorage(),
@@ -202,27 +204,50 @@ class AuthManager {
202
204
  }
203
205
  }
204
206
  /**
205
- * Refresh the access token.
207
+ * Refresh the access token. Deduplicates concurrent calls so only one
208
+ * refresh request is in-flight at a time.
206
209
  */
207
210
  async refreshToken() {
211
+ // If a refresh is already in-flight, return the same promise
212
+ if (this.refreshPromise) {
213
+ return this.refreshPromise;
214
+ }
215
+ this.refreshPromise = this._doRefreshToken();
216
+ try {
217
+ return await this.refreshPromise;
218
+ }
219
+ finally {
220
+ this.refreshPromise = null;
221
+ }
222
+ }
223
+ async _doRefreshToken() {
208
224
  const refreshToken = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
209
225
  if (!refreshToken) {
210
226
  return false;
211
227
  }
212
228
  try {
213
- // Cast httpService to proper type (needed due to mixin composition)
214
- const httpService = this.oxyServices.httpService;
215
- const response = await httpService.request({
216
- method: 'POST',
217
- url: '/api/auth/refresh',
218
- data: { refreshToken },
219
- cache: false,
229
+ await (0, asyncUtils_1.retryAsync)(async () => {
230
+ const httpService = this.oxyServices.httpService;
231
+ const response = await httpService.request({
232
+ method: 'POST',
233
+ url: '/api/auth/refresh',
234
+ data: { refreshToken },
235
+ cache: false,
236
+ });
237
+ await this.handleAuthSuccess(response, 'credentials');
238
+ }, 2, // 2 retries = 3 total attempts
239
+ 1000, // 1s base delay with exponential backoff + jitter
240
+ (error) => {
241
+ // Don't retry on 4xx client errors (invalid/revoked token)
242
+ const status = error?.status ?? error?.response?.status;
243
+ if (status && status >= 400 && status < 500)
244
+ return false;
245
+ return true;
220
246
  });
221
- await this.handleAuthSuccess(response, 'credentials');
222
247
  return true;
223
248
  }
224
249
  catch {
225
- // Refresh failed, clear session and update state
250
+ // All retry attempts exhausted, clear session
226
251
  await this.clearSession();
227
252
  this.currentUser = null;
228
253
  this.notifyListeners();
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Usage:
13
13
  * ```typescript
14
- * import { CrossDomainAuth } from '@oxyhq/services';
14
+ * import { CrossDomainAuth } from '@oxyhq/core';
15
15
  *
16
16
  * const auth = new CrossDomainAuth(oxyServices);
17
17
  *
@@ -238,7 +238,7 @@ exports.CrossDomainAuth = CrossDomainAuth;
238
238
  *
239
239
  * @example
240
240
  * ```typescript
241
- * import { createCrossDomainAuth } from '@oxyhq/services';
241
+ * import { createCrossDomainAuth } from '@oxyhq/core';
242
242
  *
243
243
  * const oxyServices = new OxyServices({ baseURL: 'https://api.oxy.so' });
244
244
  * const auth = createCrossDomainAuth(oxyServices);
@@ -19,6 +19,7 @@ const cache_1 = require("./utils/cache");
19
19
  const requestUtils_1 = require("./utils/requestUtils");
20
20
  const asyncUtils_1 = require("./utils/asyncUtils");
21
21
  const errorUtils_1 = require("./utils/errorUtils");
22
+ const debugUtils_1 = require("./shared/utils/debugUtils");
22
23
  const jwt_decode_1 = require("jwt-decode");
23
24
  const platform_1 = require("./utils/platform");
24
25
  /**
@@ -84,6 +85,7 @@ class TokenStore {
84
85
  */
85
86
  class HttpService {
86
87
  constructor(config) {
88
+ this.tokenRefreshPromise = null;
87
89
  // Performance monitoring
88
90
  this.requestMetrics = {
89
91
  totalRequests: 0,
@@ -189,7 +191,7 @@ class HttpService {
189
191
  headers['X-Native-App'] = 'true';
190
192
  }
191
193
  // Debug logging for CSRF issues
192
- if (isStateChangingMethod && __DEV__) {
194
+ if (isStateChangingMethod && (0, debugUtils_1.isDev)()) {
193
195
  console.log('[HttpService] CSRF Debug:', {
194
196
  url,
195
197
  method,
@@ -373,20 +375,20 @@ class HttpService {
373
375
  // Return cached token if available
374
376
  const cachedToken = this.tokenStore.getCsrfToken();
375
377
  if (cachedToken) {
376
- if (__DEV__)
378
+ if ((0, debugUtils_1.isDev)())
377
379
  console.log('[HttpService] Using cached CSRF token');
378
380
  return cachedToken;
379
381
  }
380
382
  // Deduplicate concurrent CSRF token fetches
381
383
  const existingPromise = this.tokenStore.getCsrfTokenFetchPromise();
382
384
  if (existingPromise) {
383
- if (__DEV__)
385
+ if ((0, debugUtils_1.isDev)())
384
386
  console.log('[HttpService] Waiting for existing CSRF fetch');
385
387
  return existingPromise;
386
388
  }
387
389
  const fetchPromise = (async () => {
388
390
  try {
389
- if (__DEV__)
391
+ if ((0, debugUtils_1.isDev)())
390
392
  console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/api/csrf-token`);
391
393
  // Use AbortController for timeout (more compatible than AbortSignal.timeout)
392
394
  const controller = new AbortController();
@@ -398,11 +400,11 @@ class HttpService {
398
400
  signal: controller.signal,
399
401
  });
400
402
  clearTimeout(timeoutId);
401
- if (__DEV__)
403
+ if ((0, debugUtils_1.isDev)())
402
404
  console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
403
405
  if (response.ok) {
404
406
  const data = await response.json();
405
- if (__DEV__)
407
+ if ((0, debugUtils_1.isDev)())
406
408
  console.log('[HttpService] CSRF response data:', data);
407
409
  const token = data.csrfToken || null;
408
410
  this.tokenStore.setCsrfToken(token);
@@ -416,13 +418,13 @@ class HttpService {
416
418
  this.logger.debug('CSRF token from header');
417
419
  return headerToken;
418
420
  }
419
- if (__DEV__)
421
+ if ((0, debugUtils_1.isDev)())
420
422
  console.log('[HttpService] CSRF fetch failed with status:', response.status);
421
423
  this.logger.warn('Failed to fetch CSRF token:', response.status);
422
424
  return null;
423
425
  }
424
426
  catch (error) {
425
- if (__DEV__)
427
+ if ((0, debugUtils_1.isDev)())
426
428
  console.log('[HttpService] CSRF fetch error:', error);
427
429
  this.logger.warn('CSRF token fetch error:', error);
428
430
  return null;
@@ -447,24 +449,17 @@ class HttpService {
447
449
  const currentTime = Math.floor(Date.now() / 1000);
448
450
  // If token expires in less than 60 seconds, refresh it
449
451
  if (decoded.exp && decoded.exp - currentTime < 60 && decoded.sessionId) {
452
+ // Deduplicate concurrent refresh attempts
453
+ if (!this.tokenRefreshPromise) {
454
+ this.tokenRefreshPromise = this._refreshTokenFromSession(decoded.sessionId);
455
+ }
450
456
  try {
451
- const refreshUrl = `${this.baseURL}/api/session/token/${decoded.sessionId}`;
452
- // Use AbortSignal.timeout for consistent timeout handling
453
- const response = await fetch(refreshUrl, {
454
- method: 'GET',
455
- headers: { 'Accept': 'application/json' },
456
- signal: AbortSignal.timeout(5000),
457
- credentials: 'include', // Include cookies for cross-origin requests
458
- });
459
- if (response.ok) {
460
- const { accessToken: newToken } = await response.json();
461
- this.tokenStore.setTokens(newToken);
462
- this.logger.debug('Token refreshed');
463
- return `Bearer ${newToken}`;
464
- }
457
+ const result = await this.tokenRefreshPromise;
458
+ if (result)
459
+ return result;
465
460
  }
466
- catch (refreshError) {
467
- this.logger.warn('Token refresh failed, using current token');
461
+ finally {
462
+ this.tokenRefreshPromise = null;
468
463
  }
469
464
  }
470
465
  return `Bearer ${accessToken}`;
@@ -474,6 +469,27 @@ class HttpService {
474
469
  return `Bearer ${accessToken}`;
475
470
  }
476
471
  }
472
+ async _refreshTokenFromSession(sessionId) {
473
+ try {
474
+ const refreshUrl = `${this.baseURL}/api/session/token/${sessionId}`;
475
+ const response = await fetch(refreshUrl, {
476
+ method: 'GET',
477
+ headers: { 'Accept': 'application/json' },
478
+ signal: AbortSignal.timeout(5000),
479
+ credentials: 'include',
480
+ });
481
+ if (response.ok) {
482
+ const { accessToken: newToken } = await response.json();
483
+ this.tokenStore.setTokens(newToken);
484
+ this.logger.debug('Token refreshed');
485
+ return `Bearer ${newToken}`;
486
+ }
487
+ }
488
+ catch (refreshError) {
489
+ this.logger.warn('Token refresh failed, using current token');
490
+ }
491
+ return null;
492
+ }
477
493
  /**
478
494
  * Unwrap standardized API response format
479
495
  */
@@ -15,6 +15,8 @@ const OxyServices_errors_1 = require("./OxyServices.errors");
15
15
  */
16
16
  class OxyServicesBase {
17
17
  constructor(...args) {
18
+ /** @internal */ this._cachedUserId = undefined;
19
+ /** @internal */ this._cachedAccessToken = null;
18
20
  const config = args[0];
19
21
  if (!config || typeof config !== 'object') {
20
22
  throw new Error('OxyConfig is required');
@@ -98,20 +100,31 @@ class OxyServicesBase {
98
100
  */
99
101
  clearTokens() {
100
102
  this.httpService.clearTokens();
103
+ this._cachedUserId = undefined;
104
+ this._cachedAccessToken = null;
101
105
  }
102
106
  /**
103
- * Get the current user ID from the access token
107
+ * Get the current user ID from the access token.
108
+ * Caches the decoded value and invalidates when the token changes.
104
109
  */
105
110
  getCurrentUserId() {
106
111
  const accessToken = this.httpService.getAccessToken();
112
+ // Return cached value if token hasn't changed
113
+ if (accessToken === this._cachedAccessToken && this._cachedUserId !== undefined) {
114
+ return this._cachedUserId;
115
+ }
116
+ this._cachedAccessToken = accessToken;
107
117
  if (!accessToken) {
118
+ this._cachedUserId = null;
108
119
  return null;
109
120
  }
110
121
  try {
111
122
  const decoded = (0, jwt_decode_1.jwtDecode)(accessToken);
112
- return decoded.userId || decoded.id || null;
123
+ this._cachedUserId = decoded.userId || decoded.id || null;
124
+ return this._cachedUserId;
113
125
  }
114
- catch (error) {
126
+ catch {
127
+ this._cachedUserId = null;
115
128
  return null;
116
129
  }
117
130
  }
@@ -43,6 +43,7 @@ exports.KeyManager = void 0;
43
43
  const elliptic_1 = require("elliptic");
44
44
  const platform_1 = require("../utils/platform");
45
45
  const loggerUtils_1 = require("../utils/loggerUtils");
46
+ const debugUtils_1 = require("../shared/utils/debugUtils");
46
47
  // Lazy imports for React Native specific modules
47
48
  let SecureStore = null;
48
49
  let ExpoCrypto = null;
@@ -77,7 +78,9 @@ const ANDROID_ACCOUNT_TYPE = 'com.oxy.account';
77
78
  async function initSecureStore() {
78
79
  if (!SecureStore) {
79
80
  try {
80
- SecureStore = await Promise.resolve().then(() => __importStar(require('expo-secure-store')));
81
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
82
+ const moduleName = 'expo-secure-store';
83
+ SecureStore = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
81
84
  }
82
85
  catch (error) {
83
86
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -110,7 +113,9 @@ function isWebPlatform() {
110
113
  }
111
114
  async function initExpoCrypto() {
112
115
  if (!ExpoCrypto) {
113
- ExpoCrypto = await Promise.resolve().then(() => __importStar(require('expo-crypto')));
116
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
117
+ const moduleName = 'expo-crypto';
118
+ ExpoCrypto = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
114
119
  }
115
120
  return ExpoCrypto;
116
121
  }
@@ -237,7 +242,7 @@ class KeyManager {
237
242
  // Update cache
238
243
  KeyManager.cachedSharedPublicKey = publicKey;
239
244
  KeyManager.cachedHasSharedIdentity = true;
240
- if (__DEV__) {
245
+ if ((0, debugUtils_1.isDev)()) {
241
246
  loggerUtils_1.logger.debug('Shared identity created successfully', { component: 'KeyManager' });
242
247
  }
243
248
  return publicKey;
@@ -271,7 +276,7 @@ class KeyManager {
271
276
  return publicKey;
272
277
  }
273
278
  catch (error) {
274
- if (__DEV__) {
279
+ if ((0, debugUtils_1.isDev)()) {
275
280
  loggerUtils_1.logger.warn('Failed to get shared public key', { component: 'KeyManager' }, error);
276
281
  }
277
282
  KeyManager.cachedSharedPublicKey = null;
@@ -304,7 +309,7 @@ class KeyManager {
304
309
  return privateKey;
305
310
  }
306
311
  catch (error) {
307
- if (__DEV__) {
312
+ if ((0, debugUtils_1.isDev)()) {
308
313
  loggerUtils_1.logger.warn('Failed to get shared private key', { component: 'KeyManager' }, error);
309
314
  }
310
315
  return null;
@@ -331,7 +336,7 @@ class KeyManager {
331
336
  return hasShared;
332
337
  }
333
338
  catch (error) {
334
- if (__DEV__) {
339
+ if ((0, debugUtils_1.isDev)()) {
335
340
  loggerUtils_1.logger.warn('Failed to check shared identity', { component: 'KeyManager' }, error);
336
341
  }
337
342
  KeyManager.cachedHasSharedIdentity = false;
@@ -374,7 +379,7 @@ class KeyManager {
374
379
  // Update cache
375
380
  KeyManager.cachedSharedPublicKey = publicKey;
376
381
  KeyManager.cachedHasSharedIdentity = true;
377
- if (__DEV__) {
382
+ if ((0, debugUtils_1.isDev)()) {
378
383
  loggerUtils_1.logger.debug('Shared identity imported successfully', { component: 'KeyManager' });
379
384
  }
380
385
  return publicKey;
@@ -408,12 +413,12 @@ class KeyManager {
408
413
  await store.setItemAsync(STORAGE_KEYS.SHARED_SESSION_ID, sessionId);
409
414
  await store.setItemAsync(STORAGE_KEYS.SHARED_SESSION_TOKEN, accessToken);
410
415
  }
411
- if (__DEV__) {
416
+ if ((0, debugUtils_1.isDev)()) {
412
417
  loggerUtils_1.logger.debug('Shared session stored successfully', { component: 'KeyManager' });
413
418
  }
414
419
  }
415
420
  catch (error) {
416
- if (__DEV__) {
421
+ if ((0, debugUtils_1.isDev)()) {
417
422
  loggerUtils_1.logger.error('Failed to store shared session', error, { component: 'KeyManager' });
418
423
  }
419
424
  throw error;
@@ -453,7 +458,7 @@ class KeyManager {
453
458
  return { sessionId, accessToken };
454
459
  }
455
460
  catch (error) {
456
- if (__DEV__) {
461
+ if ((0, debugUtils_1.isDev)()) {
457
462
  loggerUtils_1.logger.warn('Failed to get shared session', { component: 'KeyManager' }, error);
458
463
  }
459
464
  return null;
@@ -483,12 +488,12 @@ class KeyManager {
483
488
  await store.deleteItemAsync(STORAGE_KEYS.SHARED_SESSION_ID);
484
489
  await store.deleteItemAsync(STORAGE_KEYS.SHARED_SESSION_TOKEN);
485
490
  }
486
- if (__DEV__) {
491
+ if ((0, debugUtils_1.isDev)()) {
487
492
  loggerUtils_1.logger.debug('Shared session cleared successfully', { component: 'KeyManager' });
488
493
  }
489
494
  }
490
495
  catch (error) {
491
- if (__DEV__) {
496
+ if ((0, debugUtils_1.isDev)()) {
492
497
  loggerUtils_1.logger.error('Failed to clear shared session', error, { component: 'KeyManager' });
493
498
  }
494
499
  }
@@ -510,7 +515,7 @@ class KeyManager {
510
515
  // Check if we already have a shared identity
511
516
  const hasShared = await KeyManager.hasSharedIdentity();
512
517
  if (hasShared) {
513
- if (__DEV__) {
518
+ if ((0, debugUtils_1.isDev)()) {
514
519
  loggerUtils_1.logger.debug('Shared identity already exists, skipping migration', { component: 'KeyManager' });
515
520
  }
516
521
  return true;
@@ -518,20 +523,20 @@ class KeyManager {
518
523
  // Get local identity
519
524
  const privateKey = await KeyManager.getPrivateKey();
520
525
  if (!privateKey) {
521
- if (__DEV__) {
526
+ if ((0, debugUtils_1.isDev)()) {
522
527
  loggerUtils_1.logger.debug('No local identity to migrate', { component: 'KeyManager' });
523
528
  }
524
529
  return false;
525
530
  }
526
531
  // Import to shared storage
527
532
  await KeyManager.importSharedIdentity(privateKey);
528
- if (__DEV__) {
533
+ if ((0, debugUtils_1.isDev)()) {
529
534
  loggerUtils_1.logger.debug('Successfully migrated local identity to shared identity', { component: 'KeyManager' });
530
535
  }
531
536
  return true;
532
537
  }
533
538
  catch (error) {
534
- if (__DEV__) {
539
+ if ((0, debugUtils_1.isDev)()) {
535
540
  loggerUtils_1.logger.error('Failed to migrate to shared identity', error, { component: 'KeyManager' });
536
541
  }
537
542
  return false;
@@ -591,7 +596,7 @@ class KeyManager {
591
596
  catch (error) {
592
597
  // If secure store is not available, return null (no identity)
593
598
  // This allows the app to continue functioning even if secure store fails to load
594
- if (__DEV__) {
599
+ if ((0, debugUtils_1.isDev)()) {
595
600
  loggerUtils_1.logger.warn('Failed to access secure store', { component: 'KeyManager' }, error);
596
601
  }
597
602
  return null;
@@ -618,7 +623,7 @@ class KeyManager {
618
623
  // If secure store is not available, return null (no identity)
619
624
  // Cache null to avoid repeated failed attempts
620
625
  KeyManager.cachedPublicKey = null;
621
- if (__DEV__) {
626
+ if ((0, debugUtils_1.isDev)()) {
622
627
  loggerUtils_1.logger.warn('Failed to access secure store', { component: 'KeyManager' }, error);
623
628
  }
624
629
  return null;
@@ -645,7 +650,7 @@ class KeyManager {
645
650
  // If we can't check, assume no identity (safer default)
646
651
  // Cache false to avoid repeated failed attempts
647
652
  KeyManager.cachedHasIdentity = false;
648
- if (__DEV__) {
653
+ if ((0, debugUtils_1.isDev)()) {
649
654
  loggerUtils_1.logger.warn('Failed to check identity', { component: 'KeyManager' }, error);
650
655
  }
651
656
  return false;
@@ -678,12 +683,12 @@ class KeyManager {
678
683
  if (!skipBackup) {
679
684
  try {
680
685
  const backupSuccess = await KeyManager.backupIdentity();
681
- if (!backupSuccess && typeof __DEV__ !== 'undefined' && __DEV__) {
686
+ if (!backupSuccess && (0, debugUtils_1.isDev)()) {
682
687
  loggerUtils_1.logger.warn('Failed to backup identity before deletion - proceeding anyway', { component: 'KeyManager' });
683
688
  }
684
689
  }
685
690
  catch (backupError) {
686
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
691
+ if ((0, debugUtils_1.isDev)()) {
687
692
  loggerUtils_1.logger.warn('Failed to backup identity before deletion', { component: 'KeyManager' }, backupError);
688
693
  }
689
694
  }
@@ -728,7 +733,7 @@ class KeyManager {
728
733
  return true;
729
734
  }
730
735
  catch (error) {
731
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
736
+ if ((0, debugUtils_1.isDev)()) {
732
737
  loggerUtils_1.logger.error('Failed to backup identity', error, { component: 'KeyManager' });
733
738
  }
734
739
  return false;
@@ -768,7 +773,7 @@ class KeyManager {
768
773
  return true;
769
774
  }
770
775
  catch (error) {
771
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
776
+ if ((0, debugUtils_1.isDev)()) {
772
777
  loggerUtils_1.logger.error('Identity integrity check failed', error, { component: 'KeyManager' });
773
778
  }
774
779
  return false;
@@ -816,7 +821,7 @@ class KeyManager {
816
821
  return false;
817
822
  }
818
823
  catch (error) {
819
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
824
+ if ((0, debugUtils_1.isDev)()) {
820
825
  loggerUtils_1.logger.error('Failed to restore identity from backup', error, { component: 'KeyManager' });
821
826
  }
822
827
  return false;
@@ -35,7 +35,12 @@ function getRandomBytesSync(byteCount) {
35
35
  if (!expoCryptoLoadAttempted) {
36
36
  expoCryptoLoadAttempted = true;
37
37
  try {
38
- expoCryptoModule = require('expo-crypto');
38
+ // Only use require() in CJS environments (Metro/Node). In ESM (Vite/browser),
39
+ // crypto.getRandomValues exists natively so this code path is never reached.
40
+ if (typeof require !== 'undefined') {
41
+ const moduleName = 'expo-crypto';
42
+ expoCryptoModule = require(moduleName);
43
+ }
39
44
  }
40
45
  catch {
41
46
  // expo-crypto not available — expected in non-RN environments
@@ -62,7 +62,9 @@ function isNodeJS() {
62
62
  */
63
63
  async function initExpoCrypto() {
64
64
  if (!ExpoCrypto) {
65
- ExpoCrypto = await Promise.resolve().then(() => __importStar(require('expo-crypto')));
65
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
66
+ const moduleName = 'expo-crypto';
67
+ ExpoCrypto = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
66
68
  }
67
69
  return ExpoCrypto;
68
70
  }
@@ -70,25 +72,29 @@ async function initExpoCrypto() {
70
72
  * Compute SHA-256 hash of a string
71
73
  */
72
74
  async function sha256(message) {
73
- // In React Native, always use expo-crypto
74
- if (isReactNative() || !isNodeJS()) {
75
+ // In React Native, use expo-crypto
76
+ if (isReactNative()) {
75
77
  const Crypto = await initExpoCrypto();
76
78
  return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, message);
77
79
  }
78
80
  // In Node.js, use Node's crypto module
79
- // Use Function constructor to prevent Metro bundler from statically analyzing this require
80
- // This ensures the require is only evaluated in Node.js runtime, not during Metro bundling
81
- try {
82
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
83
- const getCrypto = new Function('return require("crypto")');
84
- const crypto = getCrypto();
85
- return crypto.createHash('sha256').update(message).digest('hex');
86
- }
87
- catch (error) {
88
- // Fallback to expo-crypto if Node crypto fails
89
- const Crypto = await initExpoCrypto();
90
- return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, message);
81
+ if (isNodeJS()) {
82
+ try {
83
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
84
+ const getCrypto = new Function('return require("crypto")');
85
+ const nodeCrypto = getCrypto();
86
+ return nodeCrypto.createHash('sha256').update(message).digest('hex');
87
+ }
88
+ catch {
89
+ // Fall through to Web Crypto API
90
+ }
91
91
  }
92
+ // Browser: use Web Crypto API
93
+ const encoder = new TextEncoder();
94
+ const data = encoder.encode(message);
95
+ const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', data);
96
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
97
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
92
98
  }
93
99
  class SignatureService {
94
100
  /**
@@ -96,29 +102,32 @@ class SignatureService {
96
102
  * Uses expo-crypto in React Native, crypto.randomBytes in Node.js
97
103
  */
98
104
  static async generateChallenge() {
99
- if (isReactNative() || !isNodeJS()) {
100
- // Use expo-crypto for React Native (expo-random is deprecated)
105
+ // In React Native, use expo-crypto
106
+ if (isReactNative()) {
101
107
  const Crypto = await initExpoCrypto();
102
108
  const randomBytes = await Crypto.getRandomBytesAsync(32);
103
109
  return Array.from(randomBytes)
104
110
  .map((b) => b.toString(16).padStart(2, '0'))
105
111
  .join('');
106
112
  }
107
- // Node.js fallback
108
- try {
109
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
110
- const getCrypto = new Function('return require("crypto")');
111
- const crypto = getCrypto();
112
- return crypto.randomBytes(32).toString('hex');
113
- }
114
- catch (error) {
115
- // Fallback to expo-crypto if Node crypto fails
116
- const Crypto = await initExpoCrypto();
117
- const randomBytes = await Crypto.getRandomBytesAsync(32);
118
- return Array.from(randomBytes)
119
- .map((b) => b.toString(16).padStart(2, '0'))
120
- .join('');
113
+ // In Node.js, use Node's crypto module
114
+ if (isNodeJS()) {
115
+ try {
116
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
117
+ const getCrypto = new Function('return require("crypto")');
118
+ const nodeCrypto = getCrypto();
119
+ return nodeCrypto.randomBytes(32).toString('hex');
120
+ }
121
+ catch {
122
+ // Fall through to Web Crypto API
123
+ }
121
124
  }
125
+ // Browser: use Web Crypto API
126
+ const bytes = new Uint8Array(32);
127
+ globalThis.crypto.getRandomValues(bytes);
128
+ return Array.from(bytes)
129
+ .map(b => b.toString(16).padStart(2, '0'))
130
+ .join('');
122
131
  }
123
132
  /**
124
133
  * Hash a message using SHA-256