@oxyhq/core 1.11.11 → 1.11.13
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/CrossDomainAuth.js +3 -1
- package/dist/cjs/HttpService.js +227 -51
- package/dist/cjs/OxyServices.base.js +9 -0
- package/dist/cjs/OxyServices.js +8 -3
- package/dist/cjs/crypto/index.js +3 -1
- package/dist/cjs/crypto/keyManager.js +476 -172
- package/dist/cjs/crypto/polyfill.js +14 -65
- package/dist/cjs/crypto/recoveryPhrase.js +30 -11
- package/dist/cjs/crypto/signatureService.js +25 -60
- package/dist/cjs/i18n/locales/en-US.json +46 -1
- package/dist/cjs/i18n/locales/es-ES.json +46 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +46 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +46 -1
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/mixins/OxyServices.assets.js +9 -4
- package/dist/cjs/mixins/OxyServices.auth.js +27 -0
- package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
- package/dist/cjs/mixins/OxyServices.features.js +0 -11
- package/dist/cjs/mixins/OxyServices.fedcm.js +4 -3
- package/dist/cjs/mixins/OxyServices.language.js +5 -36
- package/dist/cjs/mixins/OxyServices.redirect.js +6 -2
- package/dist/cjs/mixins/OxyServices.security.js +13 -2
- package/dist/cjs/mixins/OxyServices.user.js +70 -38
- package/dist/cjs/mixins/OxyServices.utility.js +19 -43
- package/dist/cjs/mixins/index.js +11 -3
- package/dist/cjs/utils/accountUtils.js +71 -2
- package/dist/cjs/utils/asyncUtils.js +34 -5
- package/dist/cjs/utils/deviceManager.js +5 -36
- package/dist/cjs/utils/platformCrypto.js +165 -0
- package/dist/cjs/utils/platformCrypto.native.js +123 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/CrossDomainAuth.js +3 -1
- package/dist/esm/HttpService.js +228 -52
- package/dist/esm/OxyServices.base.js +9 -0
- package/dist/esm/OxyServices.js +8 -3
- package/dist/esm/crypto/index.js +1 -1
- package/dist/esm/crypto/keyManager.js +473 -138
- package/dist/esm/crypto/polyfill.js +14 -32
- package/dist/esm/crypto/recoveryPhrase.js +30 -11
- package/dist/esm/crypto/signatureService.js +25 -27
- package/dist/esm/i18n/locales/en-US.json +46 -1
- package/dist/esm/i18n/locales/es-ES.json +46 -1
- package/dist/esm/i18n/locales/locales/en-US.json +46 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +46 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/mixins/OxyServices.assets.js +9 -4
- package/dist/esm/mixins/OxyServices.auth.js +27 -0
- package/dist/esm/mixins/OxyServices.contacts.js +47 -0
- package/dist/esm/mixins/OxyServices.features.js +0 -11
- package/dist/esm/mixins/OxyServices.fedcm.js +4 -3
- package/dist/esm/mixins/OxyServices.language.js +5 -3
- package/dist/esm/mixins/OxyServices.redirect.js +6 -2
- package/dist/esm/mixins/OxyServices.security.js +13 -2
- package/dist/esm/mixins/OxyServices.user.js +70 -38
- package/dist/esm/mixins/OxyServices.utility.js +19 -10
- package/dist/esm/mixins/index.js +11 -3
- package/dist/esm/utils/accountUtils.js +67 -1
- package/dist/esm/utils/asyncUtils.js +34 -5
- package/dist/esm/utils/deviceManager.js +5 -3
- package/dist/esm/utils/platformCrypto.js +125 -0
- package/dist/esm/utils/platformCrypto.native.js +80 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +47 -3
- package/dist/types/OxyServices.base.d.ts +7 -0
- package/dist/types/OxyServices.d.ts +36 -3
- package/dist/types/crypto/index.d.ts +1 -1
- package/dist/types/crypto/keyManager.d.ts +110 -9
- package/dist/types/crypto/polyfill.d.ts +3 -1
- package/dist/types/crypto/recoveryPhrase.d.ts +31 -7
- package/dist/types/crypto/signatureService.d.ts +4 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +6 -10
- package/dist/types/mixins/OxyServices.auth.d.ts +16 -0
- package/dist/types/mixins/OxyServices.contacts.d.ts +99 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
- package/dist/types/mixins/OxyServices.features.d.ts +2 -7
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
- package/dist/types/mixins/OxyServices.karma.d.ts +1 -0
- package/dist/types/mixins/OxyServices.language.d.ts +1 -0
- package/dist/types/mixins/OxyServices.location.d.ts +1 -0
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +1 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
- package/dist/types/mixins/OxyServices.security.d.ts +1 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.user.d.ts +40 -11
- package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
- package/dist/types/mixins/index.d.ts +52 -4
- package/dist/types/models/interfaces.d.ts +62 -3
- package/dist/types/utils/accountUtils.d.ts +41 -1
- package/dist/types/utils/asyncUtils.d.ts +6 -2
- package/dist/types/utils/platformCrypto.d.ts +87 -0
- package/dist/types/utils/platformCrypto.native.d.ts +54 -0
- package/package.json +28 -1
- package/src/CrossDomainAuth.ts +12 -10
- package/src/HttpService.ts +264 -51
- package/src/OxyServices.base.ts +10 -0
- package/src/OxyServices.ts +9 -4
- package/src/crypto/__tests__/keyManager.test.ts +336 -0
- package/src/crypto/index.ts +6 -1
- package/src/crypto/keyManager.ts +529 -151
- package/src/crypto/polyfill.ts +14 -34
- package/src/crypto/recoveryPhrase.ts +56 -17
- package/src/crypto/signatureService.ts +25 -29
- package/src/i18n/locales/en-US.json +46 -1
- package/src/i18n/locales/es-ES.json +46 -1
- package/src/index.ts +16 -3
- package/src/mixins/OxyServices.assets.ts +15 -11
- package/src/mixins/OxyServices.auth.ts +28 -0
- package/src/mixins/OxyServices.contacts.ts +73 -0
- package/src/mixins/OxyServices.features.ts +2 -12
- package/src/mixins/OxyServices.fedcm.ts +4 -3
- package/src/mixins/OxyServices.language.ts +6 -4
- package/src/mixins/OxyServices.redirect.ts +6 -2
- package/src/mixins/OxyServices.security.ts +18 -8
- package/src/mixins/OxyServices.user.ts +90 -49
- package/src/mixins/OxyServices.utility.ts +19 -10
- package/src/mixins/index.ts +58 -7
- package/src/models/interfaces.ts +65 -3
- package/src/utils/__tests__/asyncUtils.test.ts +187 -0
- package/src/utils/accountUtils.ts +82 -2
- package/src/utils/asyncUtils.ts +39 -9
- package/src/utils/deviceManager.ts +7 -4
- package/src/utils/platformCrypto.native.ts +101 -0
- package/src/utils/platformCrypto.ts +145 -0
|
@@ -168,7 +168,9 @@ class CrossDomainAuth {
|
|
|
168
168
|
* Check if FedCM is supported in current browser
|
|
169
169
|
*/
|
|
170
170
|
isFedCMSupported() {
|
|
171
|
-
|
|
171
|
+
// FedCM support is exposed both as a static and an instance method on
|
|
172
|
+
// OxyServices; the instance method is reliable across mixin composition.
|
|
173
|
+
return this.oxyServices.isFedCMSupported?.() || false;
|
|
172
174
|
}
|
|
173
175
|
/**
|
|
174
176
|
* Get recommended authentication method for current environment
|
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
|
/**
|
|
@@ -27,6 +26,32 @@ const platform_1 = require("./utils/platform");
|
|
|
27
26
|
* This is used to determine CSRF handling mode
|
|
28
27
|
*/
|
|
29
28
|
const isNativeApp = (0, platform_1.isNative)();
|
|
29
|
+
/**
|
|
30
|
+
* FNV-1a 32-bit non-cryptographic hash.
|
|
31
|
+
*
|
|
32
|
+
* Used by the cache-key generator for large payloads where full JSON
|
|
33
|
+
* inclusion would balloon the cache map keys. Content-addressed: every
|
|
34
|
+
* byte of the input contributes to the digest, so two payloads with the
|
|
35
|
+
* same top-level shape but different field values produce different keys
|
|
36
|
+
* (the previous `keys + length` heuristic collided on these).
|
|
37
|
+
*
|
|
38
|
+
* Trade-offs:
|
|
39
|
+
* - 32 bits is ample for an in-process cache (collision risk negligible
|
|
40
|
+
* at our key counts; we also prefix with method + url which further
|
|
41
|
+
* partitions the keyspace).
|
|
42
|
+
* - Not cryptographically secure — never use for security decisions.
|
|
43
|
+
* - Zero dependencies, branch-free hot loop, ~1 GiB/s on V8.
|
|
44
|
+
*/
|
|
45
|
+
function fnv1a32(str) {
|
|
46
|
+
let h = 0x811c9dc5;
|
|
47
|
+
for (let i = 0; i < str.length; i++) {
|
|
48
|
+
h ^= str.charCodeAt(i);
|
|
49
|
+
// h * 16777619 mod 2^32, written as shift-and-add for portability and
|
|
50
|
+
// to avoid 53-bit JS number truncation in the intermediate multiply.
|
|
51
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
52
|
+
}
|
|
53
|
+
return h.toString(16).padStart(8, '0');
|
|
54
|
+
}
|
|
30
55
|
/**
|
|
31
56
|
* Token store for authentication (instance-based)
|
|
32
57
|
* Each HttpService gets its own TokenStore to prevent conflicts
|
|
@@ -106,31 +131,53 @@ class HttpService {
|
|
|
106
131
|
this.requestQueue = new requestUtils_1.RequestQueue(config.maxConcurrentRequests || 10, config.requestQueueSize || 100);
|
|
107
132
|
}
|
|
108
133
|
/**
|
|
109
|
-
* Robust FormData detection that works in browser
|
|
110
|
-
*
|
|
134
|
+
* Robust FormData detection that works in browser, React Native, and
|
|
135
|
+
* Node.js polyfill environments.
|
|
136
|
+
*
|
|
137
|
+
* Why we don't use `instanceof FormData` alone:
|
|
138
|
+
* - React Native's FormData is a separate class, not the browser one —
|
|
139
|
+
* `instanceof FormData` is true only inside the JS runtime that
|
|
140
|
+
* instantiated the value (browser-side polyfills also have their own).
|
|
141
|
+
* - The Node.js `form-data` polyfill ships its own constructor.
|
|
142
|
+
*
|
|
143
|
+
* Why we explicitly reject `URLSearchParams`:
|
|
144
|
+
* - `URLSearchParams` ALSO exposes `append` / `get` / `has`, so the
|
|
145
|
+
* duck-type fallback below would have misidentified it as FormData.
|
|
146
|
+
* - We want urlencoded payloads to take the JSON-stringify path so the
|
|
147
|
+
* server receives them as `application/x-www-form-urlencoded` instead
|
|
148
|
+
* of an empty multipart body.
|
|
111
149
|
*/
|
|
112
150
|
isFormData(data) {
|
|
113
|
-
if (!data) {
|
|
151
|
+
if (!data || typeof data !== 'object') {
|
|
114
152
|
return false;
|
|
115
153
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
154
|
+
// Reject URLSearchParams up front: it shares the duck-typed surface
|
|
155
|
+
// (append / get / has) but is a fundamentally different content type.
|
|
156
|
+
// The caller routes URLSearchParams through the regular body path.
|
|
157
|
+
if (typeof URLSearchParams !== 'undefined' && data instanceof URLSearchParams) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
// Primary check: instanceof FormData. Works whenever the value was
|
|
161
|
+
// constructed by the same runtime/realm that exposes `FormData`.
|
|
162
|
+
if (typeof FormData !== 'undefined' && data instanceof FormData) {
|
|
118
163
|
return true;
|
|
119
164
|
}
|
|
120
|
-
// Fallback:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Additional check: Look for FormData-like methods
|
|
127
|
-
if (typeof data.append === 'function' &&
|
|
128
|
-
typeof data.get === 'function' &&
|
|
129
|
-
typeof data.has === 'function') {
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
165
|
+
// Fallback: detect Node / RN polyfills by constructor name. Limited to
|
|
166
|
+
// the small handful of known names so we don't accept arbitrary
|
|
167
|
+
// user-supplied objects with a coincidental `name`.
|
|
168
|
+
const constructorName = data.constructor?.name;
|
|
169
|
+
if (constructorName === 'FormData' || constructorName === 'FormDataImpl') {
|
|
170
|
+
return true;
|
|
132
171
|
}
|
|
133
|
-
|
|
172
|
+
// Last-resort duck typing — require the full FormData write surface
|
|
173
|
+
// (`append`, `get`, `has`, `getAll`, `delete`) so plain objects with
|
|
174
|
+
// an `append` method don't accidentally match.
|
|
175
|
+
const candidate = data;
|
|
176
|
+
return (typeof candidate.append === 'function' &&
|
|
177
|
+
typeof candidate.get === 'function' &&
|
|
178
|
+
typeof candidate.has === 'function' &&
|
|
179
|
+
typeof candidate.getAll === 'function' &&
|
|
180
|
+
typeof candidate.delete === 'function');
|
|
134
181
|
}
|
|
135
182
|
/**
|
|
136
183
|
* Main request method - handles everything in one place
|
|
@@ -190,9 +237,12 @@ class HttpService {
|
|
|
190
237
|
if (isNativeApp && isStateChangingMethod) {
|
|
191
238
|
headers['X-Native-App'] = 'true';
|
|
192
239
|
}
|
|
193
|
-
// Debug logging for CSRF issues
|
|
194
|
-
|
|
195
|
-
|
|
240
|
+
// Debug logging for CSRF issues — routed through the SimpleLogger so
|
|
241
|
+
// it only fires when consumers opt in via `enableLogging`. Previously
|
|
242
|
+
// this was a bare console.log that leaked noise into every host app's
|
|
243
|
+
// stdout in development.
|
|
244
|
+
if (isStateChangingMethod) {
|
|
245
|
+
this.logger.debug('CSRF Debug:', {
|
|
196
246
|
url,
|
|
197
247
|
method,
|
|
198
248
|
isNativeApp,
|
|
@@ -221,13 +271,22 @@ class HttpService {
|
|
|
221
271
|
const bodyValue = method !== 'GET' && data
|
|
222
272
|
? (isFormData ? data : JSON.stringify(data))
|
|
223
273
|
: undefined;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
274
|
+
// React Native FormData workaround:
|
|
275
|
+
// Expo SDK 56's "winter fetch" rejects RN file descriptors `{uri, type, name}`
|
|
276
|
+
// in FormDataPart conversion (`Unsupported FormDataPart implementation`).
|
|
277
|
+
// RN's native XMLHttpRequest handles those descriptors correctly, so we
|
|
278
|
+
// route multipart uploads through XHR on RN only. JSON, text, etc. still
|
|
279
|
+
// use fetch on every platform.
|
|
280
|
+
const useXhrForUpload = isFormData && (0, platform_1.isReactNative)() && typeof XMLHttpRequest !== 'undefined';
|
|
281
|
+
const response = useXhrForUpload
|
|
282
|
+
? await this.uploadViaXHR(fullUrl, method, headers, bodyValue, controller.signal, timeout)
|
|
283
|
+
: await fetch(fullUrl, {
|
|
284
|
+
method,
|
|
285
|
+
headers,
|
|
286
|
+
body: bodyValue,
|
|
287
|
+
signal: controller.signal,
|
|
288
|
+
credentials: 'include', // Include cookies for cross-origin requests (CSRF, session)
|
|
289
|
+
});
|
|
231
290
|
if (timeoutId)
|
|
232
291
|
clearTimeout(timeoutId);
|
|
233
292
|
// Handle response
|
|
@@ -367,25 +426,131 @@ class HttpService {
|
|
|
367
426
|
}
|
|
368
427
|
return result;
|
|
369
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Upload via XMLHttpRequest (React Native FormData workaround).
|
|
431
|
+
*
|
|
432
|
+
* Expo SDK 56's "winter fetch" cannot serialize RN file descriptors
|
|
433
|
+
* (`{uri, type, name}`) — `convertFormDataAsync` rejects them as
|
|
434
|
+
* `Unsupported FormDataPart implementation`. RN's native XHR streams
|
|
435
|
+
* the file from disk correctly, so multipart uploads go through XHR
|
|
436
|
+
* on RN only.
|
|
437
|
+
*
|
|
438
|
+
* Returns a standard `Response` so downstream parsing in `request()`
|
|
439
|
+
* (status checks, 401/403 retries, JSON/blob/text parsing) is identical
|
|
440
|
+
* to the fetch path.
|
|
441
|
+
*/
|
|
442
|
+
uploadViaXHR(url, method, headers, body, abortSignal, timeout) {
|
|
443
|
+
return new Promise((resolve, reject) => {
|
|
444
|
+
const xhr = new XMLHttpRequest();
|
|
445
|
+
xhr.open(method, url, true);
|
|
446
|
+
// withCredentials mirrors fetch's `credentials: 'include'` so the
|
|
447
|
+
// session cookie and CSRF cookie continue to flow.
|
|
448
|
+
xhr.withCredentials = true;
|
|
449
|
+
// Forward headers but skip Content-Type — XHR sets the multipart
|
|
450
|
+
// boundary automatically and overriding it breaks the upload.
|
|
451
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
452
|
+
if (key.toLowerCase() === 'content-type')
|
|
453
|
+
continue;
|
|
454
|
+
try {
|
|
455
|
+
xhr.setRequestHeader(key, value);
|
|
456
|
+
}
|
|
457
|
+
catch (headerError) {
|
|
458
|
+
// Some headers (e.g. forbidden header names) cannot be set —
|
|
459
|
+
// log and continue rather than failing the whole upload.
|
|
460
|
+
this.logger.warn('XHR setRequestHeader failed:', key, headerError);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
xhr.responseType = 'text';
|
|
464
|
+
if (timeout > 0) {
|
|
465
|
+
xhr.timeout = timeout;
|
|
466
|
+
}
|
|
467
|
+
const onAbort = () => {
|
|
468
|
+
try {
|
|
469
|
+
xhr.abort();
|
|
470
|
+
}
|
|
471
|
+
catch { /* xhr already finished */ }
|
|
472
|
+
};
|
|
473
|
+
if (abortSignal.aborted) {
|
|
474
|
+
reject(new DOMException('The user aborted a request.', 'AbortError'));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
abortSignal.addEventListener('abort', onAbort);
|
|
478
|
+
const cleanup = () => {
|
|
479
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
480
|
+
};
|
|
481
|
+
xhr.onload = () => {
|
|
482
|
+
cleanup();
|
|
483
|
+
const responseHeaders = HttpService.parseXHRHeaders(xhr.getAllResponseHeaders());
|
|
484
|
+
resolve(new Response(xhr.responseText, {
|
|
485
|
+
status: xhr.status,
|
|
486
|
+
statusText: xhr.statusText,
|
|
487
|
+
headers: responseHeaders,
|
|
488
|
+
}));
|
|
489
|
+
};
|
|
490
|
+
xhr.onerror = () => {
|
|
491
|
+
cleanup();
|
|
492
|
+
reject(new TypeError('Network request failed'));
|
|
493
|
+
};
|
|
494
|
+
xhr.ontimeout = () => {
|
|
495
|
+
cleanup();
|
|
496
|
+
reject(new DOMException('The request timed out.', 'TimeoutError'));
|
|
497
|
+
};
|
|
498
|
+
xhr.onabort = () => {
|
|
499
|
+
cleanup();
|
|
500
|
+
reject(new DOMException('The user aborted a request.', 'AbortError'));
|
|
501
|
+
};
|
|
502
|
+
xhr.send(body);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Parse raw header string from `XMLHttpRequest.getAllResponseHeaders()`
|
|
507
|
+
* into a `Headers`-compatible object.
|
|
508
|
+
*/
|
|
509
|
+
static parseXHRHeaders(rawHeaders) {
|
|
510
|
+
const headers = new Headers();
|
|
511
|
+
if (!rawHeaders)
|
|
512
|
+
return headers;
|
|
513
|
+
// RFC 7230 line terminator is CRLF; some XHR implementations use LF only.
|
|
514
|
+
const lines = rawHeaders.trim().split(/\r?\n/);
|
|
515
|
+
for (const line of lines) {
|
|
516
|
+
const colonIndex = line.indexOf(':');
|
|
517
|
+
if (colonIndex <= 0)
|
|
518
|
+
continue;
|
|
519
|
+
const key = line.slice(0, colonIndex).trim();
|
|
520
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
521
|
+
if (key) {
|
|
522
|
+
try {
|
|
523
|
+
headers.append(key, value);
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
// Invalid header name/value — skip.
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return headers;
|
|
531
|
+
}
|
|
370
532
|
/**
|
|
371
533
|
* Generate cache key efficiently
|
|
372
|
-
* Uses
|
|
534
|
+
* Uses a content-addressed hash for large payloads so two requests with
|
|
535
|
+
* the same shape but different values never collide on the same key
|
|
536
|
+
* (which would silently serve stale data — e.g. paginated search results,
|
|
537
|
+
* large object updates).
|
|
373
538
|
*/
|
|
374
539
|
generateCacheKey(method, url, data) {
|
|
375
540
|
if (!data || (typeof data === 'object' && Object.keys(data).length === 0)) {
|
|
376
541
|
return `${method}:${url}`;
|
|
377
542
|
}
|
|
378
|
-
// For small objects,
|
|
543
|
+
// For small objects, the full serialization IS the key — fastest and
|
|
544
|
+
// guaranteed to be content-addressed.
|
|
379
545
|
const dataStr = JSON.stringify(data);
|
|
380
546
|
if (dataStr.length < 1000) {
|
|
381
547
|
return `${method}:${url}:${dataStr}`;
|
|
382
548
|
}
|
|
383
|
-
// For large
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return `${method}:${url}:${hash}`;
|
|
549
|
+
// For large payloads, hash the full serialized string so the key remains
|
|
550
|
+
// content-addressed (any byte change yields a different hash). Previous
|
|
551
|
+
// implementation hashed `keys + length` which collided for any two
|
|
552
|
+
// payloads with the same top-level keys and serialized length.
|
|
553
|
+
return `${method}:${url}:${fnv1a32(dataStr)}`;
|
|
389
554
|
}
|
|
390
555
|
/**
|
|
391
556
|
* Build full URL with query params
|
|
@@ -412,23 +577,20 @@ class HttpService {
|
|
|
412
577
|
// Return cached token if available
|
|
413
578
|
const cachedToken = this.tokenStore.getCsrfToken();
|
|
414
579
|
if (cachedToken) {
|
|
415
|
-
|
|
416
|
-
console.log('[HttpService] Using cached CSRF token');
|
|
580
|
+
this.logger.debug('Using cached CSRF token');
|
|
417
581
|
return cachedToken;
|
|
418
582
|
}
|
|
419
583
|
// Deduplicate concurrent CSRF token fetches
|
|
420
584
|
const existingPromise = this.tokenStore.getCsrfTokenFetchPromise();
|
|
421
585
|
if (existingPromise) {
|
|
422
|
-
|
|
423
|
-
console.log('[HttpService] Waiting for existing CSRF fetch');
|
|
586
|
+
this.logger.debug('Waiting for existing CSRF fetch');
|
|
424
587
|
return existingPromise;
|
|
425
588
|
}
|
|
426
589
|
const fetchPromise = (async () => {
|
|
427
590
|
const maxAttempts = 2;
|
|
428
591
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
429
592
|
try {
|
|
430
|
-
|
|
431
|
-
console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/csrf-token`, `(attempt ${attempt})`);
|
|
593
|
+
this.logger.debug('Fetching CSRF token from:', `${this.baseURL}/csrf-token`, `(attempt ${attempt})`);
|
|
432
594
|
// Use AbortController for timeout (more compatible than AbortSignal.timeout)
|
|
433
595
|
const controller = new AbortController();
|
|
434
596
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
@@ -439,12 +601,10 @@ class HttpService {
|
|
|
439
601
|
signal: controller.signal,
|
|
440
602
|
});
|
|
441
603
|
clearTimeout(timeoutId);
|
|
442
|
-
|
|
443
|
-
console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
|
|
604
|
+
this.logger.debug('CSRF fetch response:', response.status, response.ok);
|
|
444
605
|
if (response.ok) {
|
|
445
606
|
const data = await response.json();
|
|
446
|
-
|
|
447
|
-
console.log('[HttpService] CSRF response data:', data);
|
|
607
|
+
this.logger.debug('CSRF response data:', data);
|
|
448
608
|
const token = data.csrfToken || null;
|
|
449
609
|
this.tokenStore.setCsrfToken(token);
|
|
450
610
|
this.logger.debug('CSRF token fetched');
|
|
@@ -457,13 +617,11 @@ class HttpService {
|
|
|
457
617
|
this.logger.debug('CSRF token from header');
|
|
458
618
|
return headerToken;
|
|
459
619
|
}
|
|
460
|
-
|
|
461
|
-
console.log('[HttpService] CSRF fetch failed with status:', response.status);
|
|
620
|
+
this.logger.debug('CSRF fetch failed with status:', response.status);
|
|
462
621
|
this.logger.warn('Failed to fetch CSRF token:', response.status);
|
|
463
622
|
}
|
|
464
623
|
catch (error) {
|
|
465
|
-
|
|
466
|
-
console.log('[HttpService] CSRF fetch error:', error);
|
|
624
|
+
this.logger.debug('CSRF fetch error:', error);
|
|
467
625
|
this.logger.warn('CSRF token fetch error:', error);
|
|
468
626
|
}
|
|
469
627
|
// Wait before retry (500ms)
|
|
@@ -622,6 +780,24 @@ class HttpService {
|
|
|
622
780
|
clearCacheEntry(key) {
|
|
623
781
|
this.cache.delete(key);
|
|
624
782
|
}
|
|
783
|
+
/**
|
|
784
|
+
* Delete every cache entry whose key starts with `prefix`.
|
|
785
|
+
*
|
|
786
|
+
* Used by mutations that don't know the exact downstream cache keys —
|
|
787
|
+
* e.g. `updateProfile` invalidating all `GET:/session/user/*` entries
|
|
788
|
+
* without having to track every active session ID. Returns the number of
|
|
789
|
+
* deleted entries (for observability in tests).
|
|
790
|
+
*/
|
|
791
|
+
clearCacheByPrefix(prefix) {
|
|
792
|
+
let removed = 0;
|
|
793
|
+
for (const key of this.cache.keys()) {
|
|
794
|
+
if (key.startsWith(prefix)) {
|
|
795
|
+
this.cache.delete(key);
|
|
796
|
+
removed++;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return removed;
|
|
800
|
+
}
|
|
625
801
|
getCacheStats() {
|
|
626
802
|
const cacheStats = this.cache.getStats();
|
|
627
803
|
const total = this.requestMetrics.cacheHits + this.requestMetrics.cacheMisses;
|
|
@@ -78,6 +78,15 @@ class OxyServicesBase {
|
|
|
78
78
|
clearCacheEntry(key) {
|
|
79
79
|
this.httpService.clearCacheEntry(key);
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Clear every cache entry whose key starts with `prefix`.
|
|
83
|
+
* Useful for mutations that invalidate a family of GET responses
|
|
84
|
+
* without enumerating each one (e.g. all session-user lookups after
|
|
85
|
+
* a profile update).
|
|
86
|
+
*/
|
|
87
|
+
clearCacheByPrefix(prefix) {
|
|
88
|
+
return this.httpService.clearCacheByPrefix(prefix);
|
|
89
|
+
}
|
|
81
90
|
/**
|
|
82
91
|
* Get cache statistics
|
|
83
92
|
*/
|
package/dist/cjs/OxyServices.js
CHANGED
|
@@ -39,9 +39,14 @@ const mixins_1 = require("./mixins");
|
|
|
39
39
|
* });
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
// Compose all mixins into the final OxyServices class
|
|
42
|
+
// Compose all mixins into the final OxyServices class. The composed runtime
|
|
43
|
+
// class augments OxyServicesBase with every mixin's methods (see mixins/index.ts).
|
|
44
|
+
// Statically, TypeScript sees this as a constructor producing OxyServicesBase;
|
|
45
|
+
// the additional methods are exposed via interface merging on `OxyServices` below.
|
|
43
46
|
const OxyServicesComposed = (0, mixins_1.composeOxyServices)();
|
|
44
|
-
// Export as a named class to avoid TypeScript issues with anonymous class types
|
|
47
|
+
// Export as a named class to avoid TypeScript issues with anonymous class types.
|
|
48
|
+
// We extend the composed constructor directly — its public surface is broadened
|
|
49
|
+
// to the full mixin set via the interface declaration that follows.
|
|
45
50
|
class OxyServices extends OxyServicesComposed {
|
|
46
51
|
constructor(config) {
|
|
47
52
|
super(config);
|
|
@@ -49,7 +54,7 @@ class OxyServices extends OxyServicesComposed {
|
|
|
49
54
|
}
|
|
50
55
|
exports.OxyServices = OxyServices;
|
|
51
56
|
/**
|
|
52
|
-
*
|
|
57
|
+
* Default Oxy Cloud URL — used when no `cloudURL` is provided to OxyServices.
|
|
53
58
|
*/
|
|
54
59
|
exports.OXY_CLOUD_URL = 'https://cloud.oxy.so';
|
|
55
60
|
/**
|
package/dist/cjs/crypto/index.js
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
* Handles key generation, secure storage, digital signatures, and recovery phrases.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.default = exports.RecoveryPhraseService = exports.SignatureService = exports.KeyManager = void 0;
|
|
9
|
+
exports.default = exports.RecoveryPhraseService = exports.SignatureService = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = void 0;
|
|
10
10
|
// Import polyfills first - this ensures Buffer is available for bip39 and other libraries
|
|
11
11
|
require("./polyfill");
|
|
12
12
|
var keyManager_1 = require("./keyManager");
|
|
13
13
|
Object.defineProperty(exports, "KeyManager", { enumerable: true, get: function () { return keyManager_1.KeyManager; } });
|
|
14
|
+
Object.defineProperty(exports, "IdentityAlreadyExistsError", { enumerable: true, get: function () { return keyManager_1.IdentityAlreadyExistsError; } });
|
|
15
|
+
Object.defineProperty(exports, "IdentityPersistError", { enumerable: true, get: function () { return keyManager_1.IdentityPersistError; } });
|
|
14
16
|
var signatureService_1 = require("./signatureService");
|
|
15
17
|
Object.defineProperty(exports, "SignatureService", { enumerable: true, get: function () { return signatureService_1.SignatureService; } });
|
|
16
18
|
var recoveryPhrase_1 = require("./recoveryPhrase");
|