@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.
- package/dist/cjs/AuthManager.js +35 -10
- package/dist/cjs/CrossDomainAuth.js +2 -2
- package/dist/cjs/HttpService.js +40 -24
- package/dist/cjs/OxyServices.base.js +16 -3
- package/dist/cjs/crypto/keyManager.js +29 -24
- package/dist/cjs/crypto/polyfill.js +6 -1
- package/dist/cjs/crypto/signatureService.js +40 -31
- package/dist/cjs/i18n/index.js +36 -45
- package/dist/cjs/i18n/locales/ar-SA.json +114 -115
- package/dist/cjs/i18n/locales/ca-ES.json +114 -115
- package/dist/cjs/i18n/locales/de-DE.json +114 -115
- package/dist/cjs/i18n/locales/en-US.json +936 -936
- package/dist/cjs/i18n/locales/es-ES.json +924 -924
- package/dist/cjs/i18n/locales/fr-FR.json +114 -115
- package/dist/cjs/i18n/locales/it-IT.json +114 -115
- package/dist/cjs/i18n/locales/ja-JP.json +1 -1
- package/dist/cjs/i18n/locales/ko-KR.json +114 -115
- package/dist/cjs/i18n/locales/locales/ar-SA.json +120 -0
- package/dist/cjs/i18n/locales/locales/ca-ES.json +120 -0
- package/dist/cjs/i18n/locales/locales/de-DE.json +120 -0
- package/dist/cjs/i18n/locales/locales/en-US.json +956 -0
- package/dist/cjs/i18n/locales/locales/es-ES.json +944 -0
- package/dist/cjs/i18n/locales/locales/fr-FR.json +120 -0
- package/dist/cjs/i18n/locales/locales/it-IT.json +120 -0
- package/dist/cjs/i18n/locales/locales/ja-JP.json +119 -0
- package/dist/cjs/i18n/locales/locales/ko-KR.json +120 -0
- package/dist/cjs/i18n/locales/locales/pt-PT.json +120 -0
- package/dist/cjs/i18n/locales/locales/zh-CN.json +120 -0
- package/dist/cjs/i18n/locales/pt-PT.json +114 -115
- package/dist/cjs/i18n/locales/zh-CN.json +114 -115
- package/dist/cjs/mixins/OxyServices.fedcm.js +21 -45
- package/dist/cjs/mixins/OxyServices.language.js +5 -2
- package/dist/cjs/mixins/OxyServices.popup.js +16 -6
- package/dist/cjs/mixins/OxyServices.privacy.js +2 -1
- package/dist/cjs/mixins/OxyServices.redirect.js +16 -6
- package/dist/cjs/mixins/OxyServices.security.js +3 -2
- package/dist/cjs/shared/utils/debugUtils.js +8 -1
- package/dist/cjs/utils/deviceManager.js +4 -6
- package/dist/cjs/utils/platform.js +3 -2
- package/dist/esm/AuthManager.js +35 -10
- package/dist/esm/CrossDomainAuth.js +2 -2
- package/dist/esm/HttpService.js +40 -24
- package/dist/esm/OxyServices.base.js +16 -3
- package/dist/esm/crypto/keyManager.js +29 -24
- package/dist/esm/crypto/polyfill.js +6 -1
- package/dist/esm/crypto/signatureService.js +40 -31
- package/dist/esm/i18n/index.js +11 -23
- package/dist/esm/i18n/locales/ar-SA.json +114 -115
- package/dist/esm/i18n/locales/ca-ES.json +114 -115
- package/dist/esm/i18n/locales/de-DE.json +114 -115
- package/dist/esm/i18n/locales/en-US.json +936 -936
- package/dist/esm/i18n/locales/es-ES.json +924 -924
- package/dist/esm/i18n/locales/fr-FR.json +114 -115
- package/dist/esm/i18n/locales/it-IT.json +114 -115
- package/dist/esm/i18n/locales/ja-JP.json +1 -1
- package/dist/esm/i18n/locales/ko-KR.json +114 -115
- package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
- package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
- package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
- package/dist/esm/i18n/locales/locales/en-US.json +956 -0
- package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
- package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
- package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
- package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
- package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
- package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
- package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
- package/dist/esm/i18n/locales/pt-PT.json +114 -115
- package/dist/esm/i18n/locales/zh-CN.json +114 -115
- package/dist/esm/mixins/OxyServices.fedcm.js +21 -45
- package/dist/esm/mixins/OxyServices.language.js +5 -2
- package/dist/esm/mixins/OxyServices.popup.js +16 -6
- package/dist/esm/mixins/OxyServices.privacy.js +2 -1
- package/dist/esm/mixins/OxyServices.redirect.js +16 -6
- package/dist/esm/mixins/OxyServices.security.js +3 -2
- package/dist/esm/shared/utils/debugUtils.js +8 -1
- package/dist/esm/utils/deviceManager.js +4 -6
- package/dist/esm/utils/platform.js +3 -2
- package/dist/types/AuthManager.d.ts +4 -1
- package/dist/types/CrossDomainAuth.d.ts +2 -2
- package/dist/types/HttpService.d.ts +2 -0
- package/dist/types/OxyServices.base.d.ts +4 -1
- package/dist/types/OxyServices.d.ts +13 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
- package/dist/types/mixins/OxyServices.features.d.ts +2 -0
- package/dist/types/mixins/OxyServices.fedcm.d.ts +4 -2
- package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
- package/dist/types/mixins/OxyServices.language.d.ts +2 -0
- package/dist/types/mixins/OxyServices.location.d.ts +2 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +2 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
- package/dist/types/mixins/OxyServices.security.d.ts +2 -0
- package/dist/types/mixins/OxyServices.user.d.ts +2 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +2 -0
- package/package.json +1 -2
- package/src/AuthManager.ts +42 -16
- package/src/CrossDomainAuth.ts +2 -2
- package/src/HttpService.ts +40 -26
- package/src/OxyServices.base.ts +21 -4
- package/src/OxyServices.ts +23 -2
- package/src/crypto/keyManager.ts +30 -25
- package/src/crypto/polyfill.ts +6 -1
- package/src/crypto/signatureService.ts +43 -37
- package/src/i18n/index.ts +33 -45
- package/src/index.ts +3 -0
- package/src/mixins/OxyServices.fedcm.ts +22 -48
- package/src/mixins/OxyServices.language.ts +6 -3
- package/src/mixins/OxyServices.popup.ts +16 -6
- package/src/mixins/OxyServices.privacy.ts +2 -1
- package/src/mixins/OxyServices.redirect.ts +16 -6
- package/src/mixins/OxyServices.security.ts +3 -2
- package/src/shared/utils/__tests__/debugUtils.test.ts +55 -0
- package/src/shared/utils/debugUtils.ts +6 -1
- package/src/utils/deviceManager.ts +5 -6
- package/src/utils/platform.ts +3 -2
package/dist/cjs/AuthManager.js
CHANGED
|
@@ -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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
//
|
|
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/
|
|
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/
|
|
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);
|
package/dist/cjs/HttpService.js
CHANGED
|
@@ -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 &&
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
467
|
-
this.
|
|
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
|
-
|
|
123
|
+
this._cachedUserId = decoded.userId || decoded.id || null;
|
|
124
|
+
return this._cachedUserId;
|
|
113
125
|
}
|
|
114
|
-
catch
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
74
|
-
if (isReactNative()
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|