@oxyhq/core 1.0.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/README.md +50 -0
- package/dist/cjs/AuthManager.js +361 -0
- package/dist/cjs/CrossDomainAuth.js +258 -0
- package/dist/cjs/HttpService.js +618 -0
- package/dist/cjs/OxyServices.base.js +263 -0
- package/dist/cjs/OxyServices.errors.js +22 -0
- package/dist/cjs/OxyServices.js +63 -0
- package/dist/cjs/constants/version.js +16 -0
- package/dist/cjs/crypto/index.js +20 -0
- package/dist/cjs/crypto/keyManager.js +887 -0
- package/dist/cjs/crypto/polyfill.js +64 -0
- package/dist/cjs/crypto/recoveryPhrase.js +169 -0
- package/dist/cjs/crypto/signatureService.js +296 -0
- package/dist/cjs/i18n/index.js +73 -0
- package/dist/cjs/i18n/locales/ar-SA.json +120 -0
- package/dist/cjs/i18n/locales/ca-ES.json +120 -0
- package/dist/cjs/i18n/locales/de-DE.json +120 -0
- package/dist/cjs/i18n/locales/en-US.json +956 -0
- package/dist/cjs/i18n/locales/es-ES.json +944 -0
- package/dist/cjs/i18n/locales/fr-FR.json +120 -0
- package/dist/cjs/i18n/locales/it-IT.json +120 -0
- package/dist/cjs/i18n/locales/ja-JP.json +119 -0
- package/dist/cjs/i18n/locales/ko-KR.json +120 -0
- 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 +120 -0
- package/dist/cjs/i18n/locales/zh-CN.json +120 -0
- package/dist/cjs/index.js +153 -0
- package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
- package/dist/cjs/mixins/OxyServices.assets.js +380 -0
- package/dist/cjs/mixins/OxyServices.auth.js +259 -0
- package/dist/cjs/mixins/OxyServices.developer.js +97 -0
- package/dist/cjs/mixins/OxyServices.devices.js +116 -0
- package/dist/cjs/mixins/OxyServices.features.js +309 -0
- package/dist/cjs/mixins/OxyServices.fedcm.js +435 -0
- package/dist/cjs/mixins/OxyServices.karma.js +108 -0
- package/dist/cjs/mixins/OxyServices.language.js +154 -0
- package/dist/cjs/mixins/OxyServices.location.js +43 -0
- package/dist/cjs/mixins/OxyServices.payment.js +158 -0
- package/dist/cjs/mixins/OxyServices.popup.js +371 -0
- package/dist/cjs/mixins/OxyServices.privacy.js +162 -0
- package/dist/cjs/mixins/OxyServices.redirect.js +345 -0
- package/dist/cjs/mixins/OxyServices.security.js +81 -0
- package/dist/cjs/mixins/OxyServices.user.js +355 -0
- package/dist/cjs/mixins/OxyServices.utility.js +156 -0
- package/dist/cjs/mixins/index.js +79 -0
- package/dist/cjs/mixins/mixinHelpers.js +53 -0
- package/dist/cjs/models/interfaces.js +20 -0
- package/dist/cjs/models/session.js +2 -0
- package/dist/cjs/shared/index.js +70 -0
- package/dist/cjs/shared/utils/colorUtils.js +153 -0
- package/dist/cjs/shared/utils/debugUtils.js +73 -0
- package/dist/cjs/shared/utils/errorUtils.js +183 -0
- package/dist/cjs/shared/utils/index.js +49 -0
- package/dist/cjs/shared/utils/networkUtils.js +183 -0
- package/dist/cjs/shared/utils/themeUtils.js +106 -0
- package/dist/cjs/utils/apiUtils.js +61 -0
- package/dist/cjs/utils/asyncUtils.js +194 -0
- package/dist/cjs/utils/cache.js +226 -0
- package/dist/cjs/utils/deviceManager.js +205 -0
- package/dist/cjs/utils/errorUtils.js +154 -0
- package/dist/cjs/utils/index.js +26 -0
- package/dist/cjs/utils/languageUtils.js +165 -0
- package/dist/cjs/utils/loggerUtils.js +126 -0
- package/dist/cjs/utils/platform.js +144 -0
- package/dist/cjs/utils/requestUtils.js +209 -0
- package/dist/cjs/utils/sessionUtils.js +181 -0
- package/dist/cjs/utils/validationUtils.js +173 -0
- package/dist/esm/AuthManager.js +356 -0
- package/dist/esm/CrossDomainAuth.js +253 -0
- package/dist/esm/HttpService.js +614 -0
- package/dist/esm/OxyServices.base.js +259 -0
- package/dist/esm/OxyServices.errors.js +17 -0
- package/dist/esm/OxyServices.js +59 -0
- package/dist/esm/constants/version.js +13 -0
- package/dist/esm/crypto/index.js +13 -0
- package/dist/esm/crypto/keyManager.js +850 -0
- package/dist/esm/crypto/polyfill.js +61 -0
- package/dist/esm/crypto/recoveryPhrase.js +132 -0
- package/dist/esm/crypto/signatureService.js +259 -0
- package/dist/esm/i18n/index.js +69 -0
- package/dist/esm/i18n/locales/ar-SA.json +120 -0
- package/dist/esm/i18n/locales/ca-ES.json +120 -0
- package/dist/esm/i18n/locales/de-DE.json +120 -0
- package/dist/esm/i18n/locales/en-US.json +956 -0
- package/dist/esm/i18n/locales/es-ES.json +944 -0
- package/dist/esm/i18n/locales/fr-FR.json +120 -0
- package/dist/esm/i18n/locales/it-IT.json +120 -0
- package/dist/esm/i18n/locales/ja-JP.json +119 -0
- package/dist/esm/i18n/locales/ko-KR.json +120 -0
- 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 +120 -0
- package/dist/esm/i18n/locales/zh-CN.json +120 -0
- package/dist/esm/index.js +55 -0
- package/dist/esm/mixins/OxyServices.analytics.js +46 -0
- package/dist/esm/mixins/OxyServices.assets.js +377 -0
- package/dist/esm/mixins/OxyServices.auth.js +256 -0
- package/dist/esm/mixins/OxyServices.developer.js +94 -0
- package/dist/esm/mixins/OxyServices.devices.js +113 -0
- package/dist/esm/mixins/OxyServices.features.js +306 -0
- package/dist/esm/mixins/OxyServices.fedcm.js +433 -0
- package/dist/esm/mixins/OxyServices.karma.js +105 -0
- package/dist/esm/mixins/OxyServices.language.js +118 -0
- package/dist/esm/mixins/OxyServices.location.js +40 -0
- package/dist/esm/mixins/OxyServices.payment.js +155 -0
- package/dist/esm/mixins/OxyServices.popup.js +369 -0
- package/dist/esm/mixins/OxyServices.privacy.js +159 -0
- package/dist/esm/mixins/OxyServices.redirect.js +343 -0
- package/dist/esm/mixins/OxyServices.security.js +78 -0
- package/dist/esm/mixins/OxyServices.user.js +352 -0
- package/dist/esm/mixins/OxyServices.utility.js +153 -0
- package/dist/esm/mixins/index.js +76 -0
- package/dist/esm/mixins/mixinHelpers.js +48 -0
- package/dist/esm/models/interfaces.js +17 -0
- package/dist/esm/models/session.js +1 -0
- package/dist/esm/shared/index.js +31 -0
- package/dist/esm/shared/utils/colorUtils.js +143 -0
- package/dist/esm/shared/utils/debugUtils.js +65 -0
- package/dist/esm/shared/utils/errorUtils.js +170 -0
- package/dist/esm/shared/utils/index.js +15 -0
- package/dist/esm/shared/utils/networkUtils.js +173 -0
- package/dist/esm/shared/utils/themeUtils.js +98 -0
- package/dist/esm/utils/apiUtils.js +55 -0
- package/dist/esm/utils/asyncUtils.js +179 -0
- package/dist/esm/utils/cache.js +218 -0
- package/dist/esm/utils/deviceManager.js +168 -0
- package/dist/esm/utils/errorUtils.js +146 -0
- package/dist/esm/utils/index.js +7 -0
- package/dist/esm/utils/languageUtils.js +158 -0
- package/dist/esm/utils/loggerUtils.js +115 -0
- package/dist/esm/utils/platform.js +102 -0
- package/dist/esm/utils/requestUtils.js +203 -0
- package/dist/esm/utils/sessionUtils.js +171 -0
- package/dist/esm/utils/validationUtils.js +153 -0
- package/dist/types/AuthManager.d.ts +143 -0
- package/dist/types/CrossDomainAuth.d.ts +160 -0
- package/dist/types/HttpService.d.ts +163 -0
- package/dist/types/OxyServices.base.d.ts +126 -0
- package/dist/types/OxyServices.d.ts +81 -0
- package/dist/types/OxyServices.errors.d.ts +11 -0
- package/dist/types/constants/version.d.ts +13 -0
- package/dist/types/crypto/index.d.ts +11 -0
- package/dist/types/crypto/keyManager.d.ts +189 -0
- package/dist/types/crypto/polyfill.d.ts +11 -0
- package/dist/types/crypto/recoveryPhrase.d.ts +58 -0
- package/dist/types/crypto/signatureService.d.ts +86 -0
- package/dist/types/i18n/index.d.ts +3 -0
- package/dist/types/index.d.ts +50 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +66 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +135 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +186 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +99 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +96 -0
- package/dist/types/mixins/OxyServices.features.d.ts +228 -0
- package/dist/types/mixins/OxyServices.fedcm.d.ts +200 -0
- package/dist/types/mixins/OxyServices.karma.d.ts +85 -0
- package/dist/types/mixins/OxyServices.language.d.ts +81 -0
- package/dist/types/mixins/OxyServices.location.d.ts +64 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +111 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +205 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +122 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +245 -0
- package/dist/types/mixins/OxyServices.security.d.ts +78 -0
- package/dist/types/mixins/OxyServices.user.d.ts +182 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +93 -0
- package/dist/types/mixins/index.d.ts +30 -0
- package/dist/types/mixins/mixinHelpers.d.ts +31 -0
- package/dist/types/models/interfaces.d.ts +415 -0
- package/dist/types/models/session.d.ts +27 -0
- package/dist/types/shared/index.d.ts +28 -0
- package/dist/types/shared/utils/colorUtils.d.ts +104 -0
- package/dist/types/shared/utils/debugUtils.d.ts +48 -0
- package/dist/types/shared/utils/errorUtils.d.ts +97 -0
- package/dist/types/shared/utils/index.d.ts +13 -0
- package/dist/types/shared/utils/networkUtils.d.ts +139 -0
- package/dist/types/shared/utils/themeUtils.d.ts +90 -0
- package/dist/types/utils/apiUtils.d.ts +53 -0
- package/dist/types/utils/asyncUtils.d.ts +58 -0
- package/dist/types/utils/cache.d.ts +127 -0
- package/dist/types/utils/deviceManager.d.ts +65 -0
- package/dist/types/utils/errorUtils.d.ts +46 -0
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/languageUtils.d.ts +37 -0
- package/dist/types/utils/loggerUtils.d.ts +48 -0
- package/dist/types/utils/platform.d.ts +40 -0
- package/dist/types/utils/requestUtils.d.ts +123 -0
- package/dist/types/utils/sessionUtils.d.ts +54 -0
- package/dist/types/utils/validationUtils.d.ts +85 -0
- package/package.json +84 -0
- package/src/AuthManager.ts +436 -0
- package/src/CrossDomainAuth.ts +307 -0
- package/src/HttpService.ts +752 -0
- package/src/OxyServices.base.ts +334 -0
- package/src/OxyServices.errors.ts +26 -0
- package/src/OxyServices.ts +129 -0
- package/src/constants/version.ts +15 -0
- package/src/crypto/index.ts +25 -0
- package/src/crypto/keyManager.ts +962 -0
- package/src/crypto/polyfill.ts +70 -0
- package/src/crypto/recoveryPhrase.ts +166 -0
- package/src/crypto/signatureService.ts +323 -0
- package/src/i18n/index.ts +75 -0
- package/src/i18n/locales/ar-SA.json +120 -0
- package/src/i18n/locales/ca-ES.json +120 -0
- package/src/i18n/locales/de-DE.json +120 -0
- package/src/i18n/locales/en-US.json +956 -0
- package/src/i18n/locales/es-ES.json +944 -0
- package/src/i18n/locales/fr-FR.json +120 -0
- package/src/i18n/locales/it-IT.json +120 -0
- package/src/i18n/locales/ja-JP.json +119 -0
- package/src/i18n/locales/ko-KR.json +120 -0
- package/src/i18n/locales/pt-PT.json +120 -0
- package/src/i18n/locales/zh-CN.json +120 -0
- package/src/index.ts +153 -0
- package/src/mixins/OxyServices.analytics.ts +53 -0
- package/src/mixins/OxyServices.assets.ts +412 -0
- package/src/mixins/OxyServices.auth.ts +358 -0
- package/src/mixins/OxyServices.developer.ts +114 -0
- package/src/mixins/OxyServices.devices.ts +119 -0
- package/src/mixins/OxyServices.features.ts +428 -0
- package/src/mixins/OxyServices.fedcm.ts +494 -0
- package/src/mixins/OxyServices.karma.ts +111 -0
- package/src/mixins/OxyServices.language.ts +127 -0
- package/src/mixins/OxyServices.location.ts +46 -0
- package/src/mixins/OxyServices.payment.ts +163 -0
- package/src/mixins/OxyServices.popup.ts +443 -0
- package/src/mixins/OxyServices.privacy.ts +182 -0
- package/src/mixins/OxyServices.redirect.ts +397 -0
- package/src/mixins/OxyServices.security.ts +103 -0
- package/src/mixins/OxyServices.user.ts +392 -0
- package/src/mixins/OxyServices.utility.ts +191 -0
- package/src/mixins/index.ts +91 -0
- package/src/mixins/mixinHelpers.ts +69 -0
- package/src/models/interfaces.ts +511 -0
- package/src/models/session.ts +30 -0
- package/src/shared/index.ts +82 -0
- package/src/shared/utils/colorUtils.ts +155 -0
- package/src/shared/utils/debugUtils.ts +73 -0
- package/src/shared/utils/errorUtils.ts +181 -0
- package/src/shared/utils/index.ts +59 -0
- package/src/shared/utils/networkUtils.ts +248 -0
- package/src/shared/utils/themeUtils.ts +115 -0
- package/src/types/bip39.d.ts +32 -0
- package/src/types/buffer.d.ts +97 -0
- package/src/types/color.d.ts +20 -0
- package/src/types/elliptic.d.ts +62 -0
- package/src/utils/apiUtils.ts +88 -0
- package/src/utils/asyncUtils.ts +252 -0
- package/src/utils/cache.ts +264 -0
- package/src/utils/deviceManager.ts +198 -0
- package/src/utils/errorUtils.ts +216 -0
- package/src/utils/index.ts +21 -0
- package/src/utils/languageUtils.ts +174 -0
- package/src/utils/loggerUtils.ts +153 -0
- package/src/utils/platform.ts +117 -0
- package/src/utils/requestUtils.ts +237 -0
- package/src/utils/sessionUtils.ts +206 -0
- package/src/utils/validationUtils.ts +174 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for common API patterns
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Build URL search parameters from an object
|
|
6
|
+
* @param params Object with parameter key-value pairs
|
|
7
|
+
* @returns URLSearchParams instance
|
|
8
|
+
*/
|
|
9
|
+
export function buildSearchParams(params) {
|
|
10
|
+
const searchParams = new URLSearchParams();
|
|
11
|
+
for (const [key, value] of Object.entries(params)) {
|
|
12
|
+
if (value !== undefined && value !== null) {
|
|
13
|
+
searchParams.append(key, value.toString());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return searchParams;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build URL with search parameters
|
|
20
|
+
* @param baseUrl Base URL
|
|
21
|
+
* @param params Object with parameter key-value pairs
|
|
22
|
+
* @returns Complete URL with search parameters
|
|
23
|
+
*/
|
|
24
|
+
export function buildUrl(baseUrl, params) {
|
|
25
|
+
if (!params)
|
|
26
|
+
return baseUrl;
|
|
27
|
+
const searchParams = buildSearchParams(params);
|
|
28
|
+
const queryString = searchParams.toString();
|
|
29
|
+
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build pagination search parameters
|
|
33
|
+
* @param params Pagination parameters
|
|
34
|
+
* @returns URLSearchParams with pagination
|
|
35
|
+
*/
|
|
36
|
+
export function buildPaginationParams(params) {
|
|
37
|
+
return buildSearchParams(params);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Safe JSON parsing with error handling
|
|
41
|
+
* @param data Data to parse
|
|
42
|
+
* @param fallback Fallback value if parsing fails
|
|
43
|
+
* @returns Parsed data or fallback
|
|
44
|
+
*/
|
|
45
|
+
export function safeJsonParse(data, fallback) {
|
|
46
|
+
if (typeof data === 'string') {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(data);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async utilities for common asynchronous patterns and error handling
|
|
3
|
+
*/
|
|
4
|
+
import { logger } from './loggerUtils';
|
|
5
|
+
/**
|
|
6
|
+
* Wrapper for async operations with automatic error handling
|
|
7
|
+
* Returns null on error instead of throwing
|
|
8
|
+
*/
|
|
9
|
+
export async function withErrorHandling(operation, errorHandler, context) {
|
|
10
|
+
try {
|
|
11
|
+
return await operation();
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (errorHandler) {
|
|
15
|
+
errorHandler(error);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
logger.error(`Error in ${context || 'operation'}`, error instanceof Error ? error : new Error(String(error)), {
|
|
19
|
+
component: 'asyncUtils',
|
|
20
|
+
method: 'withErrorHandling',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Execute multiple async operations in parallel with error handling
|
|
28
|
+
*/
|
|
29
|
+
export async function parallelWithErrorHandling(operations, errorHandler) {
|
|
30
|
+
const results = await Promise.allSettled(operations.map((op, index) => withErrorHandling(op, error => errorHandler?.(error, index))));
|
|
31
|
+
return results.map(result => result.status === 'fulfilled' ? result.value : null);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Retry an async operation with exponential backoff
|
|
35
|
+
*
|
|
36
|
+
* By default, does not retry on 4xx errors (client errors).
|
|
37
|
+
* Use shouldRetry callback to customize retry behavior.
|
|
38
|
+
*/
|
|
39
|
+
export async function retryAsync(operation, maxRetries = 3, baseDelay = 1000, shouldRetry) {
|
|
40
|
+
let lastError;
|
|
41
|
+
// Default shouldRetry: don't retry on 4xx errors
|
|
42
|
+
const defaultShouldRetry = (error) => {
|
|
43
|
+
// Don't retry on 4xx errors (client errors)
|
|
44
|
+
if (error?.response?.status >= 400 && error?.response?.status < 500) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
const retryCheck = shouldRetry || defaultShouldRetry;
|
|
50
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
51
|
+
try {
|
|
52
|
+
return await operation();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
lastError = error;
|
|
56
|
+
if (attempt === maxRetries) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (!retryCheck(error)) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
// Calculate delay with exponential backoff and jitter
|
|
63
|
+
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw lastError;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Debounce async function calls
|
|
71
|
+
*/
|
|
72
|
+
export function debounceAsync(func, delay) {
|
|
73
|
+
let timeoutId;
|
|
74
|
+
const lastPromise = null;
|
|
75
|
+
return (...args) => {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
timeoutId = setTimeout(async () => {
|
|
79
|
+
try {
|
|
80
|
+
const result = await func(...args);
|
|
81
|
+
resolve(result);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
reject(error);
|
|
85
|
+
}
|
|
86
|
+
}, delay);
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Throttle async function calls
|
|
92
|
+
*/
|
|
93
|
+
export function throttleAsync(func, limit, interval) {
|
|
94
|
+
let inThrottle = false;
|
|
95
|
+
let lastPromise = null;
|
|
96
|
+
return (...args) => {
|
|
97
|
+
if (inThrottle) {
|
|
98
|
+
return lastPromise;
|
|
99
|
+
}
|
|
100
|
+
inThrottle = true;
|
|
101
|
+
lastPromise = func(...args);
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
inThrottle = false;
|
|
104
|
+
}, interval);
|
|
105
|
+
return lastPromise;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Execute async operations sequentially with progress tracking
|
|
110
|
+
*/
|
|
111
|
+
export async function sequentialWithProgress(operations, onProgress) {
|
|
112
|
+
const results = [];
|
|
113
|
+
for (let i = 0; i < operations.length; i++) {
|
|
114
|
+
const result = await operations[i]();
|
|
115
|
+
results.push(result);
|
|
116
|
+
onProgress?.(i + 1, operations.length);
|
|
117
|
+
}
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Batch async operations
|
|
122
|
+
*/
|
|
123
|
+
export async function batchAsync(items, batchSize, processor) {
|
|
124
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
125
|
+
const batch = items.slice(i, i + batchSize);
|
|
126
|
+
await processor(batch);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create a cancellable async operation
|
|
131
|
+
*/
|
|
132
|
+
export function createCancellableAsync(operation) {
|
|
133
|
+
let abortController = null;
|
|
134
|
+
return {
|
|
135
|
+
execute: async () => {
|
|
136
|
+
abortController = new AbortController();
|
|
137
|
+
return await operation(abortController.signal);
|
|
138
|
+
},
|
|
139
|
+
cancel: () => {
|
|
140
|
+
abortController?.abort();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Timeout wrapper for async operations
|
|
146
|
+
*/
|
|
147
|
+
export async function withTimeout(operation, timeoutMs, timeoutMessage) {
|
|
148
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
reject(new Error(timeoutMessage || `Operation timed out after ${timeoutMs}ms`));
|
|
151
|
+
}, timeoutMs);
|
|
152
|
+
});
|
|
153
|
+
return Promise.race([operation, timeoutPromise]);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Execute async operation with loading state
|
|
157
|
+
*/
|
|
158
|
+
export async function withLoadingState(operation, setLoading) {
|
|
159
|
+
setLoading(true);
|
|
160
|
+
try {
|
|
161
|
+
return await operation();
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
setLoading(false);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create a promise that resolves after a delay
|
|
169
|
+
*/
|
|
170
|
+
export const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
171
|
+
/**
|
|
172
|
+
* Execute async operation with retry on specific errors
|
|
173
|
+
*/
|
|
174
|
+
export async function retryOnError(operation, retryableErrors, maxRetries = 3) {
|
|
175
|
+
return retryAsync(operation, maxRetries, 1000, (error) => {
|
|
176
|
+
const errorCode = error?.code || error?.status || error?.message;
|
|
177
|
+
return retryableErrors.includes(errorCode);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized cache utility with TTL support
|
|
3
|
+
*
|
|
4
|
+
* This is a production-ready cache implementation used across the codebase
|
|
5
|
+
* for consistent caching behavior and performance optimization.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* TTL-based cache implementation
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Automatic expiration based on TTL
|
|
12
|
+
* - Manual cleanup of expired entries
|
|
13
|
+
* - Statistics tracking (hits, misses, hit rate)
|
|
14
|
+
* - Type-safe generic interface
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const cache = new TTLCache<string>(5 * 60 * 1000); // 5 minutes
|
|
19
|
+
*
|
|
20
|
+
* // Set with default TTL
|
|
21
|
+
* cache.set('key', 'value');
|
|
22
|
+
*
|
|
23
|
+
* // Set with custom TTL
|
|
24
|
+
* cache.set('key', 'value', 10 * 60 * 1000); // 10 minutes
|
|
25
|
+
*
|
|
26
|
+
* // Get value
|
|
27
|
+
* const value = cache.get('key');
|
|
28
|
+
*
|
|
29
|
+
* // Get statistics
|
|
30
|
+
* const stats = cache.getStats();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class TTLCache {
|
|
34
|
+
/**
|
|
35
|
+
* Create a new TTL cache
|
|
36
|
+
* @param defaultTTL Default TTL in milliseconds (default: 5 minutes)
|
|
37
|
+
*/
|
|
38
|
+
constructor(defaultTTL = 5 * 60 * 1000) {
|
|
39
|
+
this.cache = new Map();
|
|
40
|
+
this.hits = 0;
|
|
41
|
+
this.misses = 0;
|
|
42
|
+
this.defaultTTL = defaultTTL;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get a value from cache
|
|
46
|
+
* @param key Cache key
|
|
47
|
+
* @returns Cached value or null if not found or expired
|
|
48
|
+
*/
|
|
49
|
+
get(key) {
|
|
50
|
+
const entry = this.cache.get(key);
|
|
51
|
+
if (!entry) {
|
|
52
|
+
this.misses++;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
if (now > entry.expiresAt) {
|
|
57
|
+
this.cache.delete(key);
|
|
58
|
+
this.misses++;
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
this.hits++;
|
|
62
|
+
return entry.data;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set a value in cache
|
|
66
|
+
* @param key Cache key
|
|
67
|
+
* @param data Data to cache
|
|
68
|
+
* @param ttl Optional TTL override (uses default if not provided)
|
|
69
|
+
*/
|
|
70
|
+
set(key, data, ttl) {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const expiresAt = now + (ttl || this.defaultTTL);
|
|
73
|
+
this.cache.set(key, { data, timestamp: now, expiresAt });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Delete a specific cache entry
|
|
77
|
+
* @param key Cache key
|
|
78
|
+
* @returns true if entry was deleted, false if not found
|
|
79
|
+
*/
|
|
80
|
+
delete(key) {
|
|
81
|
+
return this.cache.delete(key);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Clear all cache entries
|
|
85
|
+
*/
|
|
86
|
+
clear() {
|
|
87
|
+
this.cache.clear();
|
|
88
|
+
this.hits = 0;
|
|
89
|
+
this.misses = 0;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if a key exists and is not expired
|
|
93
|
+
* @param key Cache key
|
|
94
|
+
* @returns true if key exists and is valid
|
|
95
|
+
*/
|
|
96
|
+
has(key) {
|
|
97
|
+
const entry = this.cache.get(key);
|
|
98
|
+
if (!entry)
|
|
99
|
+
return false;
|
|
100
|
+
if (Date.now() > entry.expiresAt) {
|
|
101
|
+
this.cache.delete(key);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get all valid cache keys
|
|
108
|
+
* @returns Array of valid cache keys
|
|
109
|
+
*/
|
|
110
|
+
keys() {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const validKeys = [];
|
|
113
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
114
|
+
if (now <= entry.expiresAt) {
|
|
115
|
+
validKeys.push(key);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.cache.delete(key);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return validKeys;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Clean up expired entries
|
|
125
|
+
* @returns Number of entries removed
|
|
126
|
+
*/
|
|
127
|
+
cleanup() {
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
let removed = 0;
|
|
130
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
131
|
+
if (now > entry.expiresAt) {
|
|
132
|
+
this.cache.delete(key);
|
|
133
|
+
removed++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return removed;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get cache size (number of entries)
|
|
140
|
+
*/
|
|
141
|
+
size() {
|
|
142
|
+
return this.cache.size;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get cache statistics
|
|
146
|
+
*/
|
|
147
|
+
getStats() {
|
|
148
|
+
const total = this.hits + this.misses;
|
|
149
|
+
return {
|
|
150
|
+
size: this.cache.size,
|
|
151
|
+
hits: this.hits,
|
|
152
|
+
misses: this.misses,
|
|
153
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Reset statistics (keeps cache entries)
|
|
158
|
+
*/
|
|
159
|
+
resetStats() {
|
|
160
|
+
this.hits = 0;
|
|
161
|
+
this.misses = 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Create a TTL cache instance (convenience function)
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* const cache = createCache<string>(5 * 60 * 1000);
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export function createCache(ttl = 5 * 60 * 1000) {
|
|
173
|
+
return new TTLCache(ttl);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Global cache cleanup interval (runs every minute)
|
|
177
|
+
* This helps prevent memory leaks from expired cache entries
|
|
178
|
+
*/
|
|
179
|
+
let cleanupInterval = null;
|
|
180
|
+
const activeCaches = new Set();
|
|
181
|
+
/**
|
|
182
|
+
* Register a cache for automatic cleanup
|
|
183
|
+
* @param cache Cache instance to register
|
|
184
|
+
*/
|
|
185
|
+
export function registerCacheForCleanup(cache) {
|
|
186
|
+
activeCaches.add(cache);
|
|
187
|
+
// Start cleanup interval if not already running
|
|
188
|
+
if (!cleanupInterval) {
|
|
189
|
+
cleanupInterval = setInterval(() => {
|
|
190
|
+
for (const cache of activeCaches) {
|
|
191
|
+
cache.cleanup();
|
|
192
|
+
}
|
|
193
|
+
}, 60000); // Every minute
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Unregister a cache from automatic cleanup
|
|
198
|
+
* @param cache Cache instance to unregister
|
|
199
|
+
*/
|
|
200
|
+
export function unregisterCacheFromCleanup(cache) {
|
|
201
|
+
activeCaches.delete(cache);
|
|
202
|
+
// Stop cleanup interval if no caches are registered
|
|
203
|
+
if (activeCaches.size === 0 && cleanupInterval) {
|
|
204
|
+
clearInterval(cleanupInterval);
|
|
205
|
+
cleanupInterval = null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Stop all cleanup intervals (useful for testing)
|
|
210
|
+
* This will clear the interval and unregister all caches
|
|
211
|
+
*/
|
|
212
|
+
export function stopAllCleanupIntervals() {
|
|
213
|
+
if (cleanupInterval) {
|
|
214
|
+
clearInterval(cleanupInterval);
|
|
215
|
+
cleanupInterval = null;
|
|
216
|
+
}
|
|
217
|
+
activeCaches.clear();
|
|
218
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side device management utility
|
|
3
|
+
* Handles persistent device identification across app sessions
|
|
4
|
+
*/
|
|
5
|
+
export class DeviceManager {
|
|
6
|
+
/**
|
|
7
|
+
* Check if we're in React Native environment
|
|
8
|
+
*/
|
|
9
|
+
static isReactNative() {
|
|
10
|
+
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get appropriate storage for the platform
|
|
14
|
+
*/
|
|
15
|
+
static async getStorage() {
|
|
16
|
+
if (this.isReactNative()) {
|
|
17
|
+
try {
|
|
18
|
+
const asyncStorageModule = await import('@react-native-async-storage/async-storage');
|
|
19
|
+
const storage = asyncStorageModule.default;
|
|
20
|
+
return {
|
|
21
|
+
getItem: storage.getItem.bind(storage),
|
|
22
|
+
setItem: storage.setItem.bind(storage),
|
|
23
|
+
removeItem: storage.removeItem.bind(storage),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error('AsyncStorage not available in React Native:', error);
|
|
28
|
+
throw new Error('AsyncStorage is required in React Native environment');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Use localStorage for web
|
|
33
|
+
return {
|
|
34
|
+
getItem: async (key) => localStorage.getItem(key),
|
|
35
|
+
setItem: async (key, value) => localStorage.setItem(key, value),
|
|
36
|
+
removeItem: async (key) => localStorage.removeItem(key)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get or create device fingerprint for current device
|
|
42
|
+
*/
|
|
43
|
+
static getDeviceFingerprint() {
|
|
44
|
+
const fingerprint = {
|
|
45
|
+
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
|
|
46
|
+
platform: typeof navigator !== 'undefined' ? navigator.platform : 'unknown',
|
|
47
|
+
language: typeof navigator !== 'undefined' ? navigator.language : undefined,
|
|
48
|
+
timezone: typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions().timeZone : undefined,
|
|
49
|
+
};
|
|
50
|
+
// Add screen info if available
|
|
51
|
+
if (typeof screen !== 'undefined') {
|
|
52
|
+
fingerprint.screen = {
|
|
53
|
+
width: screen.width,
|
|
54
|
+
height: screen.height,
|
|
55
|
+
colorDepth: screen.colorDepth
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return fingerprint;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get stored device info or create new one
|
|
62
|
+
*/
|
|
63
|
+
static async getDeviceInfo() {
|
|
64
|
+
try {
|
|
65
|
+
const storage = await this.getStorage();
|
|
66
|
+
const stored = await storage.getItem(this.DEVICE_KEY);
|
|
67
|
+
if (stored) {
|
|
68
|
+
const deviceInfo = JSON.parse(stored);
|
|
69
|
+
// Update last used timestamp
|
|
70
|
+
deviceInfo.lastUsed = new Date().toISOString();
|
|
71
|
+
await this.saveDeviceInfo(deviceInfo);
|
|
72
|
+
return deviceInfo;
|
|
73
|
+
}
|
|
74
|
+
// Create new device info
|
|
75
|
+
return await this.createNewDeviceInfo();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('Error getting device info:', error);
|
|
79
|
+
return await this.createNewDeviceInfo();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create new device info and store it
|
|
84
|
+
*/
|
|
85
|
+
static async createNewDeviceInfo() {
|
|
86
|
+
const deviceInfo = {
|
|
87
|
+
deviceId: this.generateDeviceId(),
|
|
88
|
+
fingerprint: JSON.stringify(this.getDeviceFingerprint()),
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
lastUsed: new Date().toISOString()
|
|
91
|
+
};
|
|
92
|
+
await this.saveDeviceInfo(deviceInfo);
|
|
93
|
+
return deviceInfo;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Save device info to storage
|
|
97
|
+
*/
|
|
98
|
+
static async saveDeviceInfo(deviceInfo) {
|
|
99
|
+
try {
|
|
100
|
+
const storage = await this.getStorage();
|
|
101
|
+
await storage.setItem(this.DEVICE_KEY, JSON.stringify(deviceInfo));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Error saving device info:', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Update device name
|
|
109
|
+
*/
|
|
110
|
+
static async updateDeviceName(deviceName) {
|
|
111
|
+
try {
|
|
112
|
+
const deviceInfo = await this.getDeviceInfo();
|
|
113
|
+
deviceInfo.deviceName = deviceName;
|
|
114
|
+
await this.saveDeviceInfo(deviceInfo);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error('Error updating device name:', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Clear stored device info (useful for testing or reset)
|
|
122
|
+
*/
|
|
123
|
+
static async clearDeviceInfo() {
|
|
124
|
+
try {
|
|
125
|
+
const storage = await this.getStorage();
|
|
126
|
+
await storage.removeItem(this.DEVICE_KEY);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error('Error clearing device info:', error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Generate a unique device ID
|
|
134
|
+
*/
|
|
135
|
+
static generateDeviceId() {
|
|
136
|
+
// Use crypto.getRandomValues if available, otherwise fallback to Math.random
|
|
137
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
138
|
+
const array = new Uint8Array(32);
|
|
139
|
+
crypto.getRandomValues(array);
|
|
140
|
+
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Fallback for environments without crypto.getRandomValues
|
|
144
|
+
return 'device_' + Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get a user-friendly device name based on platform
|
|
149
|
+
*/
|
|
150
|
+
static getDefaultDeviceName() {
|
|
151
|
+
const fingerprint = this.getDeviceFingerprint();
|
|
152
|
+
const platform = (fingerprint.platform || '').toLowerCase();
|
|
153
|
+
if (platform.includes('win'))
|
|
154
|
+
return 'Windows Computer';
|
|
155
|
+
if (platform.includes('mac'))
|
|
156
|
+
return 'Mac Computer';
|
|
157
|
+
if (platform.includes('linux'))
|
|
158
|
+
return 'Linux Computer';
|
|
159
|
+
if (platform.includes('iphone'))
|
|
160
|
+
return 'iPhone';
|
|
161
|
+
if (platform.includes('ipad'))
|
|
162
|
+
return 'iPad';
|
|
163
|
+
if (platform.includes('android'))
|
|
164
|
+
return 'Android Device';
|
|
165
|
+
return 'Unknown Device';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
DeviceManager.DEVICE_KEY = 'oxy_device_info';
|