@oxyhq/core 1.0.2 → 1.1.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 (114) hide show
  1. package/dist/cjs/AuthManager.js +19 -9
  2. package/dist/cjs/CrossDomainAuth.js +2 -2
  3. package/dist/cjs/HttpService.js +9 -8
  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 +13 -41
  32. package/dist/cjs/mixins/OxyServices.language.js +5 -2
  33. package/dist/cjs/mixins/OxyServices.privacy.js +2 -1
  34. package/dist/cjs/mixins/OxyServices.security.js +3 -2
  35. package/dist/cjs/shared/utils/debugUtils.js +8 -1
  36. package/dist/cjs/utils/deviceManager.js +3 -1
  37. package/dist/cjs/utils/platform.js +3 -2
  38. package/dist/esm/AuthManager.js +19 -9
  39. package/dist/esm/CrossDomainAuth.js +2 -2
  40. package/dist/esm/HttpService.js +9 -8
  41. package/dist/esm/OxyServices.base.js +16 -3
  42. package/dist/esm/crypto/keyManager.js +29 -24
  43. package/dist/esm/crypto/polyfill.js +6 -1
  44. package/dist/esm/crypto/signatureService.js +40 -31
  45. package/dist/esm/i18n/index.js +11 -23
  46. package/dist/esm/i18n/locales/ar-SA.json +114 -115
  47. package/dist/esm/i18n/locales/ca-ES.json +114 -115
  48. package/dist/esm/i18n/locales/de-DE.json +114 -115
  49. package/dist/esm/i18n/locales/en-US.json +936 -936
  50. package/dist/esm/i18n/locales/es-ES.json +924 -924
  51. package/dist/esm/i18n/locales/fr-FR.json +114 -115
  52. package/dist/esm/i18n/locales/it-IT.json +114 -115
  53. package/dist/esm/i18n/locales/ja-JP.json +1 -1
  54. package/dist/esm/i18n/locales/ko-KR.json +114 -115
  55. package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
  56. package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
  57. package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
  58. package/dist/esm/i18n/locales/locales/en-US.json +956 -0
  59. package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
  60. package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
  61. package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
  62. package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
  63. package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
  64. package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
  65. package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
  66. package/dist/esm/i18n/locales/pt-PT.json +114 -115
  67. package/dist/esm/i18n/locales/zh-CN.json +114 -115
  68. package/dist/esm/mixins/OxyServices.fedcm.js +13 -41
  69. package/dist/esm/mixins/OxyServices.language.js +5 -2
  70. package/dist/esm/mixins/OxyServices.privacy.js +2 -1
  71. package/dist/esm/mixins/OxyServices.security.js +3 -2
  72. package/dist/esm/shared/utils/debugUtils.js +8 -1
  73. package/dist/esm/utils/deviceManager.js +3 -1
  74. package/dist/esm/utils/platform.js +3 -2
  75. package/dist/types/CrossDomainAuth.d.ts +2 -2
  76. package/dist/types/OxyServices.base.d.ts +4 -1
  77. package/dist/types/OxyServices.d.ts +13 -0
  78. package/dist/types/index.d.ts +3 -0
  79. package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
  80. package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
  81. package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
  82. package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
  83. package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
  84. package/dist/types/mixins/OxyServices.features.d.ts +2 -0
  85. package/dist/types/mixins/OxyServices.fedcm.d.ts +4 -2
  86. package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
  87. package/dist/types/mixins/OxyServices.language.d.ts +2 -0
  88. package/dist/types/mixins/OxyServices.location.d.ts +2 -0
  89. package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
  90. package/dist/types/mixins/OxyServices.popup.d.ts +2 -0
  91. package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
  92. package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
  93. package/dist/types/mixins/OxyServices.security.d.ts +2 -0
  94. package/dist/types/mixins/OxyServices.user.d.ts +2 -0
  95. package/dist/types/mixins/OxyServices.utility.d.ts +2 -0
  96. package/package.json +1 -2
  97. package/src/AuthManager.ts +25 -15
  98. package/src/CrossDomainAuth.ts +2 -2
  99. package/src/HttpService.ts +9 -8
  100. package/src/OxyServices.base.ts +21 -4
  101. package/src/OxyServices.ts +23 -2
  102. package/src/crypto/keyManager.ts +30 -25
  103. package/src/crypto/polyfill.ts +6 -1
  104. package/src/crypto/signatureService.ts +43 -37
  105. package/src/i18n/index.ts +33 -45
  106. package/src/index.ts +3 -0
  107. package/src/mixins/OxyServices.fedcm.ts +14 -44
  108. package/src/mixins/OxyServices.language.ts +6 -3
  109. package/src/mixins/OxyServices.privacy.ts +2 -1
  110. package/src/mixins/OxyServices.security.ts +3 -2
  111. package/src/shared/utils/__tests__/debugUtils.test.ts +55 -0
  112. package/src/shared/utils/debugUtils.ts +6 -1
  113. package/src/utils/deviceManager.ts +4 -2
  114. package/src/utils/platform.ts +3 -2
@@ -151,7 +151,10 @@ function OxyServicesFedCMMixin(Base) {
151
151
  }
152
152
  const clientId = this.getClientId();
153
153
  debug.log('Silent SSO: Starting for', clientId);
154
- // First try silent mediation (no UI) - works if user previously consented
154
+ // Only try silent mediation (no UI) - works if user previously consented.
155
+ // We intentionally do NOT fall back to optional mediation here because
156
+ // this runs on app startup — showing browser UI without user action is bad UX.
157
+ // Optional/interactive mediation should only happen when the user clicks "Sign In".
155
158
  let credential = null;
156
159
  try {
157
160
  const nonce = this.generateNonce();
@@ -165,33 +168,13 @@ function OxyServicesFedCMMixin(Base) {
165
168
  debug.log('Silent SSO: Silent mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
166
169
  }
167
170
  catch (silentError) {
168
- // Silent mediation failed - this is expected if user hasn't consented before or is in quiet period
169
171
  const errorName = silentError instanceof Error ? silentError.name : 'Unknown';
170
172
  const errorMessage = silentError instanceof Error ? silentError.message : String(silentError);
171
- debug.log('Silent SSO: Silent mediation error (will try optional):', { name: errorName, message: errorMessage });
172
- }
173
- // If silent failed, try optional mediation which shows browser UI if needed
174
- if (!credential || !credential.token) {
175
- try {
176
- const nonce = this.generateNonce();
177
- debug.log('Silent SSO: Trying optional mediation (may show browser UI)...');
178
- credential = await this.requestIdentityCredential({
179
- configURL: this.constructor.DEFAULT_CONFIG_URL,
180
- clientId,
181
- nonce,
182
- mediation: 'optional',
183
- });
184
- debug.log('Silent SSO: Optional mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
185
- }
186
- catch (optionalError) {
187
- const errorName = optionalError instanceof Error ? optionalError.name : 'Unknown';
188
- const errorMessage = optionalError instanceof Error ? optionalError.message : String(optionalError);
189
- debug.log('Silent SSO: Optional mediation also failed:', { name: errorName, message: errorMessage });
190
- return null;
191
- }
173
+ debug.log('Silent SSO: Silent mediation failed:', { name: errorName, message: errorMessage });
174
+ return null;
192
175
  }
193
176
  if (!credential || !credential.token) {
194
- debug.log('Silent SSO: No credential returned (user may have dismissed prompt or is not logged in at IdP)');
177
+ debug.log('Silent SSO: No credential returned (user not logged in at IdP or hasn\'t consented)');
195
178
  return null;
196
179
  }
197
180
  debug.log('Silent SSO: Got credential, exchanging for session...');
@@ -341,28 +324,17 @@ function OxyServicesFedCMMixin(Base) {
341
324
  * @private
342
325
  */
343
326
  async exchangeIdTokenForSession(idToken) {
344
- debug.log('exchangeIdTokenForSession: Starting exchange...');
345
- debug.log('exchangeIdTokenForSession: Token length:', idToken?.length);
346
- debug.log('exchangeIdTokenForSession: Token preview:', idToken?.substring(0, 50) + '...');
327
+ debug.log('Exchanging ID token for session...');
347
328
  try {
348
329
  const response = await this.makeRequest('POST', '/api/fedcm/exchange', { id_token: idToken }, { cache: false });
349
- debug.log('exchangeIdTokenForSession: Response received:', {
350
- hasResponse: !!response,
351
- hasSessionId: !!response?.sessionId,
330
+ debug.log('Token exchange complete:', {
331
+ hasSession: !!response?.sessionId,
352
332
  hasUser: !!response?.user,
353
- hasAccessToken: !!response?.accessToken,
354
- userId: response?.user?.id,
355
- username: response?.user?.username,
356
- responseKeys: response ? Object.keys(response) : [],
357
333
  });
358
334
  return response;
359
335
  }
360
336
  catch (error) {
361
- debug.error('exchangeIdTokenForSession: Error:', {
362
- name: error instanceof Error ? error.name : 'Unknown',
363
- message: error instanceof Error ? error.message : String(error),
364
- stack: error instanceof Error ? error.stack : undefined,
365
- });
337
+ debug.error('Token exchange failed:', error instanceof Error ? error.message : String(error));
366
338
  throw error;
367
339
  }
368
340
  }
@@ -427,9 +399,9 @@ function OxyServicesFedCMMixin(Base) {
427
399
  }
428
400
  },
429
401
  _a.DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json',
430
- _a.FEDCM_TIMEOUT = 60000 // 1 minute for interactive
402
+ _a.FEDCM_TIMEOUT = 15000 // 15 seconds for interactive
431
403
  ,
432
- _a.FEDCM_SILENT_TIMEOUT = 10000 // 10 seconds for silent mediation
404
+ _a.FEDCM_SILENT_TIMEOUT = 3000 // 3 seconds for silent mediation
433
405
  ,
434
406
  _a;
435
407
  }
@@ -38,6 +38,7 @@ exports.OxyServicesLanguageMixin = OxyServicesLanguageMixin;
38
38
  * Language Methods Mixin
39
39
  */
40
40
  const languageUtils_1 = require("../utils/languageUtils");
41
+ const debugUtils_1 = require("../shared/utils/debugUtils");
41
42
  function OxyServicesLanguageMixin(Base) {
42
43
  return class extends Base {
43
44
  constructor(...args) {
@@ -50,7 +51,9 @@ function OxyServicesLanguageMixin(Base) {
50
51
  const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
51
52
  if (isReactNative) {
52
53
  try {
53
- const asyncStorageModule = await Promise.resolve().then(() => __importStar(require('@react-native-async-storage/async-storage')));
54
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
55
+ const moduleName = '@react-native-async-storage/async-storage';
56
+ const asyncStorageModule = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
54
57
  const storage = asyncStorageModule.default;
55
58
  return {
56
59
  getItem: storage.getItem.bind(storage),
@@ -113,7 +116,7 @@ function OxyServicesLanguageMixin(Base) {
113
116
  return null;
114
117
  }
115
118
  catch (error) {
116
- if (__DEV__) {
119
+ if ((0, debugUtils_1.isDev)()) {
117
120
  console.warn('Failed to get current language:', error);
118
121
  }
119
122
  return null;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OxyServicesPrivacyMixin = OxyServicesPrivacyMixin;
4
+ const debugUtils_1 = require("../shared/utils/debugUtils");
4
5
  function OxyServicesPrivacyMixin(Base) {
5
6
  return class extends Base {
6
7
  constructor(...args) {
@@ -28,7 +29,7 @@ function OxyServicesPrivacyMixin(Base) {
28
29
  }
29
30
  catch (error) {
30
31
  // If there's an error, assume not in list to avoid breaking functionality
31
- if (__DEV__) {
32
+ if ((0, debugUtils_1.isDev)()) {
32
33
  console.warn('Error checking user list:', error);
33
34
  }
34
35
  return false;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OxyServicesSecurityMixin = OxyServicesSecurityMixin;
4
+ const debugUtils_1 = require("../shared/utils/debugUtils");
4
5
  function OxyServicesSecurityMixin(Base) {
5
6
  return class extends Base {
6
7
  constructor(...args) {
@@ -55,7 +56,7 @@ function OxyServicesSecurityMixin(Base) {
55
56
  catch (error) {
56
57
  // Don't throw - logging failures shouldn't break user flow
57
58
  // But log for monitoring
58
- if (__DEV__) {
59
+ if ((0, debugUtils_1.isDev)()) {
59
60
  console.warn('[OxyServices] Failed to log private key exported event:', error);
60
61
  }
61
62
  }
@@ -72,7 +73,7 @@ function OxyServicesSecurityMixin(Base) {
72
73
  catch (error) {
73
74
  // Don't throw - logging failures shouldn't break user flow
74
75
  // But log for monitoring
75
- if (__DEV__) {
76
+ if ((0, debugUtils_1.isDev)()) {
76
77
  console.warn('[OxyServices] Failed to log backup created event:', error);
77
78
  }
78
79
  }
@@ -13,7 +13,14 @@ exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.deb
13
13
  * Check if running in development mode
14
14
  */
15
15
  const isDev = () => {
16
- return typeof __DEV__ !== 'undefined' && __DEV__;
16
+ if (typeof __DEV__ !== 'undefined')
17
+ return __DEV__;
18
+ try {
19
+ return typeof process !== 'undefined' && process.env?.NODE_ENV === 'development';
20
+ }
21
+ catch {
22
+ return false;
23
+ }
17
24
  };
18
25
  exports.isDev = isDev;
19
26
  /**
@@ -51,7 +51,9 @@ class DeviceManager {
51
51
  static async getStorage() {
52
52
  if (this.isReactNative()) {
53
53
  try {
54
- const asyncStorageModule = await Promise.resolve().then(() => __importStar(require('@react-native-async-storage/async-storage')));
54
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
55
+ const moduleName = '@react-native-async-storage/async-storage';
56
+ const asyncStorageModule = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
55
57
  const storage = asyncStorageModule.default;
56
58
  return {
57
59
  getItem: storage.getItem.bind(storage),
@@ -134,8 +134,9 @@ async function initPlatformFromReactNative() {
134
134
  return; // Already initialized
135
135
  }
136
136
  try {
137
- // Dynamic import to avoid bundler issues
138
- const { Platform } = await Promise.resolve().then(() => __importStar(require('react-native')));
137
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
138
+ const moduleName = 'react-native';
139
+ const { Platform } = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
139
140
  setPlatformOS(Platform.OS);
140
141
  }
141
142
  catch {
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * @module core/AuthManager
8
8
  */
9
+ import { retryAsync } from './utils/asyncUtils';
9
10
  /**
10
11
  * Storage keys used by AuthManager.
11
12
  */
@@ -206,19 +207,28 @@ export class AuthManager {
206
207
  return false;
207
208
  }
208
209
  try {
209
- // Cast httpService to proper type (needed due to mixin composition)
210
- const httpService = this.oxyServices.httpService;
211
- const response = await httpService.request({
212
- method: 'POST',
213
- url: '/api/auth/refresh',
214
- data: { refreshToken },
215
- cache: false,
210
+ await retryAsync(async () => {
211
+ const httpService = this.oxyServices.httpService;
212
+ const response = await httpService.request({
213
+ method: 'POST',
214
+ url: '/api/auth/refresh',
215
+ data: { refreshToken },
216
+ cache: false,
217
+ });
218
+ await this.handleAuthSuccess(response, 'credentials');
219
+ }, 2, // 2 retries = 3 total attempts
220
+ 1000, // 1s base delay with exponential backoff + jitter
221
+ (error) => {
222
+ // Don't retry on 4xx client errors (invalid/revoked token)
223
+ const status = error?.status ?? error?.response?.status;
224
+ if (status && status >= 400 && status < 500)
225
+ return false;
226
+ return true;
216
227
  });
217
- await this.handleAuthSuccess(response, 'credentials');
218
228
  return true;
219
229
  }
220
230
  catch {
221
- // Refresh failed, clear session and update state
231
+ // All retry attempts exhausted, clear session
222
232
  await this.clearSession();
223
233
  this.currentUser = null;
224
234
  this.notifyListeners();
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Usage:
12
12
  * ```typescript
13
- * import { CrossDomainAuth } from '@oxyhq/services';
13
+ * import { CrossDomainAuth } from '@oxyhq/core';
14
14
  *
15
15
  * const auth = new CrossDomainAuth(oxyServices);
16
16
  *
@@ -233,7 +233,7 @@ export class CrossDomainAuth {
233
233
  *
234
234
  * @example
235
235
  * ```typescript
236
- * import { createCrossDomainAuth } from '@oxyhq/services';
236
+ * import { createCrossDomainAuth } from '@oxyhq/core';
237
237
  *
238
238
  * const oxyServices = new OxyServices({ baseURL: 'https://api.oxy.so' });
239
239
  * const auth = createCrossDomainAuth(oxyServices);
@@ -16,6 +16,7 @@ import { TTLCache, registerCacheForCleanup } from './utils/cache';
16
16
  import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/requestUtils';
17
17
  import { retryAsync } from './utils/asyncUtils';
18
18
  import { handleHttpError } from './utils/errorUtils';
19
+ import { isDev } from './shared/utils/debugUtils';
19
20
  import { jwtDecode } from 'jwt-decode';
20
21
  import { isNative, getPlatformOS } from './utils/platform';
21
22
  /**
@@ -186,7 +187,7 @@ export class HttpService {
186
187
  headers['X-Native-App'] = 'true';
187
188
  }
188
189
  // Debug logging for CSRF issues
189
- if (isStateChangingMethod && __DEV__) {
190
+ if (isStateChangingMethod && isDev()) {
190
191
  console.log('[HttpService] CSRF Debug:', {
191
192
  url,
192
193
  method,
@@ -370,20 +371,20 @@ export class HttpService {
370
371
  // Return cached token if available
371
372
  const cachedToken = this.tokenStore.getCsrfToken();
372
373
  if (cachedToken) {
373
- if (__DEV__)
374
+ if (isDev())
374
375
  console.log('[HttpService] Using cached CSRF token');
375
376
  return cachedToken;
376
377
  }
377
378
  // Deduplicate concurrent CSRF token fetches
378
379
  const existingPromise = this.tokenStore.getCsrfTokenFetchPromise();
379
380
  if (existingPromise) {
380
- if (__DEV__)
381
+ if (isDev())
381
382
  console.log('[HttpService] Waiting for existing CSRF fetch');
382
383
  return existingPromise;
383
384
  }
384
385
  const fetchPromise = (async () => {
385
386
  try {
386
- if (__DEV__)
387
+ if (isDev())
387
388
  console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/api/csrf-token`);
388
389
  // Use AbortController for timeout (more compatible than AbortSignal.timeout)
389
390
  const controller = new AbortController();
@@ -395,11 +396,11 @@ export class HttpService {
395
396
  signal: controller.signal,
396
397
  });
397
398
  clearTimeout(timeoutId);
398
- if (__DEV__)
399
+ if (isDev())
399
400
  console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
400
401
  if (response.ok) {
401
402
  const data = await response.json();
402
- if (__DEV__)
403
+ if (isDev())
403
404
  console.log('[HttpService] CSRF response data:', data);
404
405
  const token = data.csrfToken || null;
405
406
  this.tokenStore.setCsrfToken(token);
@@ -413,13 +414,13 @@ export class HttpService {
413
414
  this.logger.debug('CSRF token from header');
414
415
  return headerToken;
415
416
  }
416
- if (__DEV__)
417
+ if (isDev())
417
418
  console.log('[HttpService] CSRF fetch failed with status:', response.status);
418
419
  this.logger.warn('Failed to fetch CSRF token:', response.status);
419
420
  return null;
420
421
  }
421
422
  catch (error) {
422
- if (__DEV__)
423
+ if (isDev())
423
424
  console.log('[HttpService] CSRF fetch error:', error);
424
425
  this.logger.warn('CSRF token fetch error:', error);
425
426
  return null;
@@ -12,6 +12,8 @@ import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServ
12
12
  */
13
13
  export class OxyServicesBase {
14
14
  constructor(...args) {
15
+ /** @internal */ this._cachedUserId = undefined;
16
+ /** @internal */ this._cachedAccessToken = null;
15
17
  const config = args[0];
16
18
  if (!config || typeof config !== 'object') {
17
19
  throw new Error('OxyConfig is required');
@@ -95,20 +97,31 @@ export class OxyServicesBase {
95
97
  */
96
98
  clearTokens() {
97
99
  this.httpService.clearTokens();
100
+ this._cachedUserId = undefined;
101
+ this._cachedAccessToken = null;
98
102
  }
99
103
  /**
100
- * Get the current user ID from the access token
104
+ * Get the current user ID from the access token.
105
+ * Caches the decoded value and invalidates when the token changes.
101
106
  */
102
107
  getCurrentUserId() {
103
108
  const accessToken = this.httpService.getAccessToken();
109
+ // Return cached value if token hasn't changed
110
+ if (accessToken === this._cachedAccessToken && this._cachedUserId !== undefined) {
111
+ return this._cachedUserId;
112
+ }
113
+ this._cachedAccessToken = accessToken;
104
114
  if (!accessToken) {
115
+ this._cachedUserId = null;
105
116
  return null;
106
117
  }
107
118
  try {
108
119
  const decoded = jwtDecode(accessToken);
109
- return decoded.userId || decoded.id || null;
120
+ this._cachedUserId = decoded.userId || decoded.id || null;
121
+ return this._cachedUserId;
110
122
  }
111
- catch (error) {
123
+ catch {
124
+ this._cachedUserId = null;
112
125
  return null;
113
126
  }
114
127
  }
@@ -7,6 +7,7 @@
7
7
  import { ec as EC } from 'elliptic';
8
8
  import { isWeb, isIOS, isAndroid } from '../utils/platform';
9
9
  import { logger } from '../utils/loggerUtils';
10
+ import { isDev } from '../shared/utils/debugUtils';
10
11
  // Lazy imports for React Native specific modules
11
12
  let SecureStore = null;
12
13
  let ExpoCrypto = null;
@@ -41,7 +42,9 @@ const ANDROID_ACCOUNT_TYPE = 'com.oxy.account';
41
42
  async function initSecureStore() {
42
43
  if (!SecureStore) {
43
44
  try {
44
- SecureStore = await import('expo-secure-store');
45
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
46
+ const moduleName = 'expo-secure-store';
47
+ SecureStore = await import(moduleName);
45
48
  }
46
49
  catch (error) {
47
50
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -74,7 +77,9 @@ function isWebPlatform() {
74
77
  }
75
78
  async function initExpoCrypto() {
76
79
  if (!ExpoCrypto) {
77
- ExpoCrypto = await import('expo-crypto');
80
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
81
+ const moduleName = 'expo-crypto';
82
+ ExpoCrypto = await import(moduleName);
78
83
  }
79
84
  return ExpoCrypto;
80
85
  }
@@ -201,7 +206,7 @@ export class KeyManager {
201
206
  // Update cache
202
207
  KeyManager.cachedSharedPublicKey = publicKey;
203
208
  KeyManager.cachedHasSharedIdentity = true;
204
- if (__DEV__) {
209
+ if (isDev()) {
205
210
  logger.debug('Shared identity created successfully', { component: 'KeyManager' });
206
211
  }
207
212
  return publicKey;
@@ -235,7 +240,7 @@ export class KeyManager {
235
240
  return publicKey;
236
241
  }
237
242
  catch (error) {
238
- if (__DEV__) {
243
+ if (isDev()) {
239
244
  logger.warn('Failed to get shared public key', { component: 'KeyManager' }, error);
240
245
  }
241
246
  KeyManager.cachedSharedPublicKey = null;
@@ -268,7 +273,7 @@ export class KeyManager {
268
273
  return privateKey;
269
274
  }
270
275
  catch (error) {
271
- if (__DEV__) {
276
+ if (isDev()) {
272
277
  logger.warn('Failed to get shared private key', { component: 'KeyManager' }, error);
273
278
  }
274
279
  return null;
@@ -295,7 +300,7 @@ export class KeyManager {
295
300
  return hasShared;
296
301
  }
297
302
  catch (error) {
298
- if (__DEV__) {
303
+ if (isDev()) {
299
304
  logger.warn('Failed to check shared identity', { component: 'KeyManager' }, error);
300
305
  }
301
306
  KeyManager.cachedHasSharedIdentity = false;
@@ -338,7 +343,7 @@ export class KeyManager {
338
343
  // Update cache
339
344
  KeyManager.cachedSharedPublicKey = publicKey;
340
345
  KeyManager.cachedHasSharedIdentity = true;
341
- if (__DEV__) {
346
+ if (isDev()) {
342
347
  logger.debug('Shared identity imported successfully', { component: 'KeyManager' });
343
348
  }
344
349
  return publicKey;
@@ -372,12 +377,12 @@ export class KeyManager {
372
377
  await store.setItemAsync(STORAGE_KEYS.SHARED_SESSION_ID, sessionId);
373
378
  await store.setItemAsync(STORAGE_KEYS.SHARED_SESSION_TOKEN, accessToken);
374
379
  }
375
- if (__DEV__) {
380
+ if (isDev()) {
376
381
  logger.debug('Shared session stored successfully', { component: 'KeyManager' });
377
382
  }
378
383
  }
379
384
  catch (error) {
380
- if (__DEV__) {
385
+ if (isDev()) {
381
386
  logger.error('Failed to store shared session', error, { component: 'KeyManager' });
382
387
  }
383
388
  throw error;
@@ -417,7 +422,7 @@ export class KeyManager {
417
422
  return { sessionId, accessToken };
418
423
  }
419
424
  catch (error) {
420
- if (__DEV__) {
425
+ if (isDev()) {
421
426
  logger.warn('Failed to get shared session', { component: 'KeyManager' }, error);
422
427
  }
423
428
  return null;
@@ -447,12 +452,12 @@ export class KeyManager {
447
452
  await store.deleteItemAsync(STORAGE_KEYS.SHARED_SESSION_ID);
448
453
  await store.deleteItemAsync(STORAGE_KEYS.SHARED_SESSION_TOKEN);
449
454
  }
450
- if (__DEV__) {
455
+ if (isDev()) {
451
456
  logger.debug('Shared session cleared successfully', { component: 'KeyManager' });
452
457
  }
453
458
  }
454
459
  catch (error) {
455
- if (__DEV__) {
460
+ if (isDev()) {
456
461
  logger.error('Failed to clear shared session', error, { component: 'KeyManager' });
457
462
  }
458
463
  }
@@ -474,7 +479,7 @@ export class KeyManager {
474
479
  // Check if we already have a shared identity
475
480
  const hasShared = await KeyManager.hasSharedIdentity();
476
481
  if (hasShared) {
477
- if (__DEV__) {
482
+ if (isDev()) {
478
483
  logger.debug('Shared identity already exists, skipping migration', { component: 'KeyManager' });
479
484
  }
480
485
  return true;
@@ -482,20 +487,20 @@ export class KeyManager {
482
487
  // Get local identity
483
488
  const privateKey = await KeyManager.getPrivateKey();
484
489
  if (!privateKey) {
485
- if (__DEV__) {
490
+ if (isDev()) {
486
491
  logger.debug('No local identity to migrate', { component: 'KeyManager' });
487
492
  }
488
493
  return false;
489
494
  }
490
495
  // Import to shared storage
491
496
  await KeyManager.importSharedIdentity(privateKey);
492
- if (__DEV__) {
497
+ if (isDev()) {
493
498
  logger.debug('Successfully migrated local identity to shared identity', { component: 'KeyManager' });
494
499
  }
495
500
  return true;
496
501
  }
497
502
  catch (error) {
498
- if (__DEV__) {
503
+ if (isDev()) {
499
504
  logger.error('Failed to migrate to shared identity', error, { component: 'KeyManager' });
500
505
  }
501
506
  return false;
@@ -555,7 +560,7 @@ export class KeyManager {
555
560
  catch (error) {
556
561
  // If secure store is not available, return null (no identity)
557
562
  // This allows the app to continue functioning even if secure store fails to load
558
- if (__DEV__) {
563
+ if (isDev()) {
559
564
  logger.warn('Failed to access secure store', { component: 'KeyManager' }, error);
560
565
  }
561
566
  return null;
@@ -582,7 +587,7 @@ export class KeyManager {
582
587
  // If secure store is not available, return null (no identity)
583
588
  // Cache null to avoid repeated failed attempts
584
589
  KeyManager.cachedPublicKey = null;
585
- if (__DEV__) {
590
+ if (isDev()) {
586
591
  logger.warn('Failed to access secure store', { component: 'KeyManager' }, error);
587
592
  }
588
593
  return null;
@@ -609,7 +614,7 @@ export class KeyManager {
609
614
  // If we can't check, assume no identity (safer default)
610
615
  // Cache false to avoid repeated failed attempts
611
616
  KeyManager.cachedHasIdentity = false;
612
- if (__DEV__) {
617
+ if (isDev()) {
613
618
  logger.warn('Failed to check identity', { component: 'KeyManager' }, error);
614
619
  }
615
620
  return false;
@@ -642,12 +647,12 @@ export class KeyManager {
642
647
  if (!skipBackup) {
643
648
  try {
644
649
  const backupSuccess = await KeyManager.backupIdentity();
645
- if (!backupSuccess && typeof __DEV__ !== 'undefined' && __DEV__) {
650
+ if (!backupSuccess && isDev()) {
646
651
  logger.warn('Failed to backup identity before deletion - proceeding anyway', { component: 'KeyManager' });
647
652
  }
648
653
  }
649
654
  catch (backupError) {
650
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
655
+ if (isDev()) {
651
656
  logger.warn('Failed to backup identity before deletion', { component: 'KeyManager' }, backupError);
652
657
  }
653
658
  }
@@ -692,7 +697,7 @@ export class KeyManager {
692
697
  return true;
693
698
  }
694
699
  catch (error) {
695
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
700
+ if (isDev()) {
696
701
  logger.error('Failed to backup identity', error, { component: 'KeyManager' });
697
702
  }
698
703
  return false;
@@ -732,7 +737,7 @@ export class KeyManager {
732
737
  return true;
733
738
  }
734
739
  catch (error) {
735
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
740
+ if (isDev()) {
736
741
  logger.error('Identity integrity check failed', error, { component: 'KeyManager' });
737
742
  }
738
743
  return false;
@@ -780,7 +785,7 @@ export class KeyManager {
780
785
  return false;
781
786
  }
782
787
  catch (error) {
783
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
788
+ if (isDev()) {
784
789
  logger.error('Failed to restore identity from backup', error, { component: 'KeyManager' });
785
790
  }
786
791
  return false;
@@ -31,7 +31,12 @@ function getRandomBytesSync(byteCount) {
31
31
  if (!expoCryptoLoadAttempted) {
32
32
  expoCryptoLoadAttempted = true;
33
33
  try {
34
- expoCryptoModule = require('expo-crypto');
34
+ // Only use require() in CJS environments (Metro/Node). In ESM (Vite/browser),
35
+ // crypto.getRandomValues exists natively so this code path is never reached.
36
+ if (typeof require !== 'undefined') {
37
+ const moduleName = 'expo-crypto';
38
+ expoCryptoModule = require(moduleName);
39
+ }
35
40
  }
36
41
  catch {
37
42
  // expo-crypto not available — expected in non-RN environments