@oxyhq/core 1.11.11 → 1.11.12
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/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +13 -18
- package/dist/cjs/crypto/signatureService.js +11 -11
- package/dist/cjs/mixins/OxyServices.user.js +11 -0
- package/dist/cjs/utils/asyncUtils.js +34 -5
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +13 -18
- package/dist/esm/crypto/keyManager.js +3 -3
- package/dist/esm/crypto/polyfill.js +1 -1
- package/dist/esm/crypto/signatureService.js +12 -12
- package/dist/esm/mixins/OxyServices.language.js +1 -1
- package/dist/esm/mixins/OxyServices.user.js +11 -0
- package/dist/esm/utils/asyncUtils.js +34 -5
- package/dist/esm/utils/deviceManager.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +12 -0
- package/dist/types/utils/asyncUtils.d.ts +6 -2
- package/package.json +1 -1
- package/src/HttpService.ts +13 -11
- package/src/crypto/keyManager.ts +3 -3
- package/src/crypto/polyfill.ts +1 -1
- package/src/crypto/signatureService.ts +13 -12
- package/src/mixins/OxyServices.language.ts +1 -1
- package/src/mixins/OxyServices.user.ts +18 -0
- package/src/utils/__tests__/asyncUtils.test.ts +187 -0
- package/src/utils/asyncUtils.ts +39 -9
- package/src/utils/deviceManager.ts +1 -1
package/dist/cjs/HttpService.js
CHANGED
|
@@ -19,7 +19,6 @@ 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");
|
|
23
22
|
const jwt_decode_1 = require("jwt-decode");
|
|
24
23
|
const platform_1 = require("./utils/platform");
|
|
25
24
|
/**
|
|
@@ -190,9 +189,12 @@ class HttpService {
|
|
|
190
189
|
if (isNativeApp && isStateChangingMethod) {
|
|
191
190
|
headers['X-Native-App'] = 'true';
|
|
192
191
|
}
|
|
193
|
-
// Debug logging for CSRF issues
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
// Debug logging for CSRF issues — routed through the SimpleLogger so
|
|
193
|
+
// it only fires when consumers opt in via `enableLogging`. Previously
|
|
194
|
+
// this was a bare console.log that leaked noise into every host app's
|
|
195
|
+
// stdout in development.
|
|
196
|
+
if (isStateChangingMethod) {
|
|
197
|
+
this.logger.debug('CSRF Debug:', {
|
|
196
198
|
url,
|
|
197
199
|
method,
|
|
198
200
|
isNativeApp,
|
|
@@ -412,23 +414,20 @@ class HttpService {
|
|
|
412
414
|
// Return cached token if available
|
|
413
415
|
const cachedToken = this.tokenStore.getCsrfToken();
|
|
414
416
|
if (cachedToken) {
|
|
415
|
-
|
|
416
|
-
console.log('[HttpService] Using cached CSRF token');
|
|
417
|
+
this.logger.debug('Using cached CSRF token');
|
|
417
418
|
return cachedToken;
|
|
418
419
|
}
|
|
419
420
|
// Deduplicate concurrent CSRF token fetches
|
|
420
421
|
const existingPromise = this.tokenStore.getCsrfTokenFetchPromise();
|
|
421
422
|
if (existingPromise) {
|
|
422
|
-
|
|
423
|
-
console.log('[HttpService] Waiting for existing CSRF fetch');
|
|
423
|
+
this.logger.debug('Waiting for existing CSRF fetch');
|
|
424
424
|
return existingPromise;
|
|
425
425
|
}
|
|
426
426
|
const fetchPromise = (async () => {
|
|
427
427
|
const maxAttempts = 2;
|
|
428
428
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
429
429
|
try {
|
|
430
|
-
|
|
431
|
-
console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/csrf-token`, `(attempt ${attempt})`);
|
|
430
|
+
this.logger.debug('Fetching CSRF token from:', `${this.baseURL}/csrf-token`, `(attempt ${attempt})`);
|
|
432
431
|
// Use AbortController for timeout (more compatible than AbortSignal.timeout)
|
|
433
432
|
const controller = new AbortController();
|
|
434
433
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
@@ -439,12 +438,10 @@ class HttpService {
|
|
|
439
438
|
signal: controller.signal,
|
|
440
439
|
});
|
|
441
440
|
clearTimeout(timeoutId);
|
|
442
|
-
|
|
443
|
-
console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
|
|
441
|
+
this.logger.debug('CSRF fetch response:', response.status, response.ok);
|
|
444
442
|
if (response.ok) {
|
|
445
443
|
const data = await response.json();
|
|
446
|
-
|
|
447
|
-
console.log('[HttpService] CSRF response data:', data);
|
|
444
|
+
this.logger.debug('CSRF response data:', data);
|
|
448
445
|
const token = data.csrfToken || null;
|
|
449
446
|
this.tokenStore.setCsrfToken(token);
|
|
450
447
|
this.logger.debug('CSRF token fetched');
|
|
@@ -457,13 +454,11 @@ class HttpService {
|
|
|
457
454
|
this.logger.debug('CSRF token from header');
|
|
458
455
|
return headerToken;
|
|
459
456
|
}
|
|
460
|
-
|
|
461
|
-
console.log('[HttpService] CSRF fetch failed with status:', response.status);
|
|
457
|
+
this.logger.debug('CSRF fetch failed with status:', response.status);
|
|
462
458
|
this.logger.warn('Failed to fetch CSRF token:', response.status);
|
|
463
459
|
}
|
|
464
460
|
catch (error) {
|
|
465
|
-
|
|
466
|
-
console.log('[HttpService] CSRF fetch error:', error);
|
|
461
|
+
this.logger.debug('CSRF fetch error:', error);
|
|
467
462
|
this.logger.warn('CSRF token fetch error:', error);
|
|
468
463
|
}
|
|
469
464
|
// Wait before retry (500ms)
|
|
@@ -43,20 +43,24 @@ exports.SignatureService = void 0;
|
|
|
43
43
|
const elliptic_1 = require("elliptic");
|
|
44
44
|
const keyManager_1 = require("./keyManager");
|
|
45
45
|
const platform_1 = require("../utils/platform");
|
|
46
|
-
// Lazy
|
|
46
|
+
// Lazy imports for platform-specific crypto
|
|
47
47
|
let ExpoCrypto = null;
|
|
48
|
+
let NodeCrypto = null;
|
|
48
49
|
const ec = new elliptic_1.ec('secp256k1');
|
|
49
|
-
/**
|
|
50
|
-
* Initialize expo-crypto module
|
|
51
|
-
*/
|
|
52
50
|
async function initExpoCrypto() {
|
|
53
51
|
if (!ExpoCrypto) {
|
|
54
|
-
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
55
52
|
const moduleName = 'expo-crypto';
|
|
56
53
|
ExpoCrypto = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
|
|
57
54
|
}
|
|
58
55
|
return ExpoCrypto;
|
|
59
56
|
}
|
|
57
|
+
async function initNodeCrypto() {
|
|
58
|
+
if (!NodeCrypto) {
|
|
59
|
+
const moduleName = 'crypto';
|
|
60
|
+
NodeCrypto = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
|
|
61
|
+
}
|
|
62
|
+
return NodeCrypto;
|
|
63
|
+
}
|
|
60
64
|
/**
|
|
61
65
|
* Compute SHA-256 hash of a string
|
|
62
66
|
*/
|
|
@@ -66,10 +70,9 @@ async function sha256(message) {
|
|
|
66
70
|
const Crypto = await initExpoCrypto();
|
|
67
71
|
return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, message);
|
|
68
72
|
}
|
|
69
|
-
// In Node.js, use Node's crypto module
|
|
70
73
|
if ((0, platform_1.isNodeJS)()) {
|
|
71
74
|
try {
|
|
72
|
-
const nodeCrypto = await
|
|
75
|
+
const nodeCrypto = await initNodeCrypto();
|
|
73
76
|
return nodeCrypto.createHash('sha256').update(message).digest('hex');
|
|
74
77
|
}
|
|
75
78
|
catch {
|
|
@@ -97,12 +100,9 @@ class SignatureService {
|
|
|
97
100
|
.map((b) => b.toString(16).padStart(2, '0'))
|
|
98
101
|
.join('');
|
|
99
102
|
}
|
|
100
|
-
// In Node.js, use Node's crypto module
|
|
101
|
-
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
102
103
|
if ((0, platform_1.isNodeJS)()) {
|
|
103
104
|
try {
|
|
104
|
-
const
|
|
105
|
-
const nodeCrypto = await Promise.resolve(`${cryptoModuleName}`).then(s => __importStar(require(s)));
|
|
105
|
+
const nodeCrypto = await initNodeCrypto();
|
|
106
106
|
return nodeCrypto.randomBytes(32).toString('hex');
|
|
107
107
|
}
|
|
108
108
|
catch {
|
|
@@ -21,6 +21,17 @@ function OxyServicesUserMixin(Base) {
|
|
|
21
21
|
throw this.handleError(error);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Lightweight username lookup for login flows.
|
|
26
|
+
* Returns minimal public info: exists, color, avatar, displayName.
|
|
27
|
+
* Faster than getProfileByUsername — no stats, no formatting.
|
|
28
|
+
*/
|
|
29
|
+
async lookupUsername(username) {
|
|
30
|
+
return await this.makeRequest('GET', `/auth/lookup/${encodeURIComponent(username)}`, undefined, {
|
|
31
|
+
cache: true,
|
|
32
|
+
cacheTTL: 60 * 1000, // 1 minute cache
|
|
33
|
+
});
|
|
34
|
+
}
|
|
24
35
|
/**
|
|
25
36
|
* Search user profiles
|
|
26
37
|
*/
|
|
@@ -44,18 +44,47 @@ async function parallelWithErrorHandling(operations, errorHandler) {
|
|
|
44
44
|
const results = await Promise.allSettled(operations.map((op, index) => withErrorHandling(op, error => errorHandler?.(error, index))));
|
|
45
45
|
return results.map(result => result.status === 'fulfilled' ? result.value : null);
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract an HTTP status code from an error value, tolerating both the
|
|
49
|
+
* axios-style nested shape (`error.response.status`) and the flat shape
|
|
50
|
+
* produced by {@link handleHttpError} / fetch-based clients (`error.status`).
|
|
51
|
+
*
|
|
52
|
+
* Centralising this lookup prevents retry predicates from silently falling
|
|
53
|
+
* through when one of the two shapes is missing, which previously caused
|
|
54
|
+
* @oxyhq/core to retry 4xx responses and turn sub-10ms failures into
|
|
55
|
+
* multi-second stalls for every missing-resource lookup.
|
|
56
|
+
*/
|
|
57
|
+
function extractHttpStatus(error) {
|
|
58
|
+
if (!error || typeof error !== 'object')
|
|
59
|
+
return undefined;
|
|
60
|
+
const candidate = error;
|
|
61
|
+
const flat = candidate.status;
|
|
62
|
+
if (typeof flat === 'number' && Number.isFinite(flat))
|
|
63
|
+
return flat;
|
|
64
|
+
const nested = candidate.response?.status;
|
|
65
|
+
if (typeof nested === 'number' && Number.isFinite(nested))
|
|
66
|
+
return nested;
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
47
69
|
/**
|
|
48
70
|
* Retry an async operation with exponential backoff
|
|
49
71
|
*
|
|
50
|
-
* By default, does not retry on 4xx errors (client errors).
|
|
51
|
-
*
|
|
72
|
+
* By default, does not retry on 4xx errors (client errors). The default
|
|
73
|
+
* predicate accepts both the axios-style `error.response.status` and the
|
|
74
|
+
* flat `error.status` shape produced by {@link handleHttpError}, so callers
|
|
75
|
+
* never accidentally retry a deterministic client failure.
|
|
76
|
+
*
|
|
77
|
+
* Use the `shouldRetry` callback to customize retry behavior.
|
|
52
78
|
*/
|
|
53
79
|
async function retryAsync(operation, maxRetries = 3, baseDelay = 1000, shouldRetry) {
|
|
54
80
|
let lastError;
|
|
55
|
-
// Default shouldRetry: don't retry on 4xx errors
|
|
81
|
+
// Default shouldRetry: don't retry on 4xx errors (client errors).
|
|
82
|
+
// Checks BOTH `error.status` (flat shape from handleHttpError / fetch
|
|
83
|
+
// clients) AND `error.response.status` (axios-style shape) so neither
|
|
84
|
+
// representation can leak a client error into the retry loop.
|
|
56
85
|
const defaultShouldRetry = (error) => {
|
|
57
|
-
|
|
58
|
-
if (
|
|
86
|
+
const status = extractHttpStatus(error);
|
|
87
|
+
if (status !== undefined && status >= 400 && status < 500) {
|
|
59
88
|
return false;
|
|
60
89
|
}
|
|
61
90
|
return true;
|