@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
|
@@ -32,40 +32,44 @@ function isNodeJS(): boolean {
|
|
|
32
32
|
*/
|
|
33
33
|
async function initExpoCrypto(): Promise<typeof import('expo-crypto')> {
|
|
34
34
|
if (!ExpoCrypto) {
|
|
35
|
-
|
|
35
|
+
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
36
|
+
const moduleName = 'expo-crypto';
|
|
37
|
+
ExpoCrypto = await import(moduleName);
|
|
36
38
|
}
|
|
37
|
-
return ExpoCrypto
|
|
39
|
+
return ExpoCrypto!;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* Compute SHA-256 hash of a string
|
|
42
44
|
*/
|
|
43
45
|
async function sha256(message: string): Promise<string> {
|
|
44
|
-
// In React Native,
|
|
45
|
-
if (isReactNative()
|
|
46
|
+
// In React Native, use expo-crypto
|
|
47
|
+
if (isReactNative()) {
|
|
46
48
|
const Crypto = await initExpoCrypto();
|
|
47
49
|
return Crypto.digestStringAsync(
|
|
48
50
|
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
49
51
|
message
|
|
50
52
|
);
|
|
51
53
|
}
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
// In Node.js, use Node's crypto module
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const Crypto = await initExpoCrypto();
|
|
64
|
-
return Crypto.digestStringAsync(
|
|
65
|
-
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
66
|
-
message
|
|
67
|
-
);
|
|
56
|
+
if (isNodeJS()) {
|
|
57
|
+
try {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
59
|
+
const getCrypto = new Function('return require("crypto")');
|
|
60
|
+
const nodeCrypto = getCrypto();
|
|
61
|
+
return nodeCrypto.createHash('sha256').update(message).digest('hex');
|
|
62
|
+
} catch {
|
|
63
|
+
// Fall through to Web Crypto API
|
|
64
|
+
}
|
|
68
65
|
}
|
|
66
|
+
|
|
67
|
+
// Browser: use Web Crypto API
|
|
68
|
+
const encoder = new TextEncoder();
|
|
69
|
+
const data = encoder.encode(message);
|
|
70
|
+
const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', data);
|
|
71
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
72
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export interface SignedMessage {
|
|
@@ -87,29 +91,33 @@ export class SignatureService {
|
|
|
87
91
|
* Uses expo-crypto in React Native, crypto.randomBytes in Node.js
|
|
88
92
|
*/
|
|
89
93
|
static async generateChallenge(): Promise<string> {
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
// In React Native, use expo-crypto
|
|
95
|
+
if (isReactNative()) {
|
|
92
96
|
const Crypto = await initExpoCrypto();
|
|
93
97
|
const randomBytes = await Crypto.getRandomBytesAsync(32);
|
|
94
98
|
return Array.from(randomBytes)
|
|
95
99
|
.map((b: number) => b.toString(16).padStart(2, '0'))
|
|
96
100
|
.join('');
|
|
97
101
|
}
|
|
98
|
-
|
|
99
|
-
// Node.js
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return Array.from(randomBytes)
|
|
110
|
-
.map((b: number) => b.toString(16).padStart(2, '0'))
|
|
111
|
-
.join('');
|
|
102
|
+
|
|
103
|
+
// In Node.js, use Node's crypto module
|
|
104
|
+
if (isNodeJS()) {
|
|
105
|
+
try {
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
107
|
+
const getCrypto = new Function('return require("crypto")');
|
|
108
|
+
const nodeCrypto = getCrypto();
|
|
109
|
+
return nodeCrypto.randomBytes(32).toString('hex');
|
|
110
|
+
} catch {
|
|
111
|
+
// Fall through to Web Crypto API
|
|
112
|
+
}
|
|
112
113
|
}
|
|
114
|
+
|
|
115
|
+
// Browser: use Web Crypto API
|
|
116
|
+
const bytes = new Uint8Array(32);
|
|
117
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
118
|
+
return Array.from(bytes)
|
|
119
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
120
|
+
.join('');
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
/**
|
|
@@ -319,5 +327,3 @@ export class SignatureService {
|
|
|
319
327
|
}
|
|
320
328
|
|
|
321
329
|
export default SignatureService;
|
|
322
|
-
|
|
323
|
-
|
package/src/i18n/index.ts
CHANGED
|
@@ -1,52 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
13
|
-
const itIT = require('./locales/it-IT.json') as Record<string, any>;
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
15
|
-
const ptPT = require('./locales/pt-PT.json') as Record<string, any>;
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
17
|
-
const jaJP = require('./locales/ja-JP.json') as Record<string, any>;
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
19
|
-
const koKR = require('./locales/ko-KR.json') as Record<string, any>;
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
|
-
const zhCN = require('./locales/zh-CN.json') as Record<string, any>;
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
23
|
-
const arSA = require('./locales/ar-SA.json') as Record<string, any>;
|
|
1
|
+
import enUS from './locales/en-US.json';
|
|
2
|
+
import esES from './locales/es-ES.json';
|
|
3
|
+
import caES from './locales/ca-ES.json';
|
|
4
|
+
import frFR from './locales/fr-FR.json';
|
|
5
|
+
import deDE from './locales/de-DE.json';
|
|
6
|
+
import itIT from './locales/it-IT.json';
|
|
7
|
+
import ptPT from './locales/pt-PT.json';
|
|
8
|
+
import jaJP from './locales/ja-JP.json';
|
|
9
|
+
import koKR from './locales/ko-KR.json';
|
|
10
|
+
import zhCN from './locales/zh-CN.json';
|
|
11
|
+
import arSA from './locales/ar-SA.json';
|
|
24
12
|
|
|
25
13
|
export type LocaleDict = Record<string, any>;
|
|
26
14
|
|
|
27
15
|
const DICTS: Record<string, LocaleDict> = {
|
|
28
|
-
'en': enUS
|
|
29
|
-
'en-US': enUS
|
|
30
|
-
'es': esES
|
|
31
|
-
'es-ES': esES
|
|
32
|
-
'ca': caES
|
|
33
|
-
'ca-ES': caES
|
|
34
|
-
'fr': frFR
|
|
35
|
-
'fr-FR': frFR
|
|
36
|
-
'de': deDE
|
|
37
|
-
'de-DE': deDE
|
|
38
|
-
'it': itIT
|
|
39
|
-
'it-IT': itIT
|
|
40
|
-
'pt': ptPT
|
|
41
|
-
'pt-PT': ptPT
|
|
42
|
-
'ja': jaJP
|
|
43
|
-
'ja-JP': jaJP
|
|
44
|
-
'ko': koKR
|
|
45
|
-
'ko-KR': koKR
|
|
46
|
-
'zh': zhCN
|
|
47
|
-
'zh-CN': zhCN
|
|
48
|
-
'ar': arSA
|
|
49
|
-
'ar-SA': arSA
|
|
16
|
+
'en': enUS,
|
|
17
|
+
'en-US': enUS,
|
|
18
|
+
'es': esES,
|
|
19
|
+
'es-ES': esES,
|
|
20
|
+
'ca': caES,
|
|
21
|
+
'ca-ES': caES,
|
|
22
|
+
'fr': frFR,
|
|
23
|
+
'fr-FR': frFR,
|
|
24
|
+
'de': deDE,
|
|
25
|
+
'de-DE': deDE,
|
|
26
|
+
'it': itIT,
|
|
27
|
+
'it-IT': itIT,
|
|
28
|
+
'pt': ptPT,
|
|
29
|
+
'pt-PT': ptPT,
|
|
30
|
+
'ja': jaJP,
|
|
31
|
+
'ja-JP': jaJP,
|
|
32
|
+
'ko': koKR,
|
|
33
|
+
'ko-KR': koKR,
|
|
34
|
+
'zh': zhCN,
|
|
35
|
+
'zh-CN': zhCN,
|
|
36
|
+
'ar': arSA,
|
|
37
|
+
'ar-SA': arSA,
|
|
50
38
|
};
|
|
51
39
|
|
|
52
40
|
const FALLBACK = 'en-US';
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,9 @@ export type { StorageAdapter, AuthStateChangeCallback, AuthMethod, AuthManagerCo
|
|
|
27
27
|
|
|
28
28
|
export { CrossDomainAuth, createCrossDomainAuth } from './CrossDomainAuth';
|
|
29
29
|
export type { CrossDomainAuthOptions } from './CrossDomainAuth';
|
|
30
|
+
export type { FedCMAuthOptions, FedCMConfig } from './mixins/OxyServices.fedcm';
|
|
31
|
+
export type { PopupAuthOptions } from './mixins/OxyServices.popup';
|
|
32
|
+
export type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
|
|
30
33
|
|
|
31
34
|
// --- Crypto / Identity ---
|
|
32
35
|
export { KeyManager, SignatureService, RecoveryPhraseService } from './crypto';
|
|
@@ -49,8 +49,8 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
49
49
|
super(...(args as [any]));
|
|
50
50
|
}
|
|
51
51
|
public static readonly DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json';
|
|
52
|
-
public static readonly FEDCM_TIMEOUT =
|
|
53
|
-
public static readonly FEDCM_SILENT_TIMEOUT =
|
|
52
|
+
public static readonly FEDCM_TIMEOUT = 15000; // 15 seconds for interactive
|
|
53
|
+
public static readonly FEDCM_SILENT_TIMEOUT = 3000; // 3 seconds for silent mediation
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Check if FedCM is supported in the current browser
|
|
@@ -180,7 +180,10 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
180
180
|
const clientId = this.getClientId();
|
|
181
181
|
debug.log('Silent SSO: Starting for', clientId);
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// Only try silent mediation (no UI) - works if user previously consented.
|
|
184
|
+
// We intentionally do NOT fall back to optional mediation here because
|
|
185
|
+
// this runs on app startup — showing browser UI without user action is bad UX.
|
|
186
|
+
// Optional/interactive mediation should only happen when the user clicks "Sign In".
|
|
184
187
|
let credential: { token: string } | null = null;
|
|
185
188
|
|
|
186
189
|
try {
|
|
@@ -196,36 +199,14 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
196
199
|
|
|
197
200
|
debug.log('Silent SSO: Silent mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
|
|
198
201
|
} catch (silentError) {
|
|
199
|
-
// Silent mediation failed - this is expected if user hasn't consented before or is in quiet period
|
|
200
202
|
const errorName = silentError instanceof Error ? silentError.name : 'Unknown';
|
|
201
203
|
const errorMessage = silentError instanceof Error ? silentError.message : String(silentError);
|
|
202
|
-
debug.log('Silent SSO: Silent mediation
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// If silent failed, try optional mediation which shows browser UI if needed
|
|
206
|
-
if (!credential || !credential.token) {
|
|
207
|
-
try {
|
|
208
|
-
const nonce = this.generateNonce();
|
|
209
|
-
debug.log('Silent SSO: Trying optional mediation (may show browser UI)...');
|
|
210
|
-
|
|
211
|
-
credential = await this.requestIdentityCredential({
|
|
212
|
-
configURL: (this.constructor as any).DEFAULT_CONFIG_URL,
|
|
213
|
-
clientId,
|
|
214
|
-
nonce,
|
|
215
|
-
mediation: 'optional',
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
debug.log('Silent SSO: Optional mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
|
|
219
|
-
} catch (optionalError) {
|
|
220
|
-
const errorName = optionalError instanceof Error ? optionalError.name : 'Unknown';
|
|
221
|
-
const errorMessage = optionalError instanceof Error ? optionalError.message : String(optionalError);
|
|
222
|
-
debug.log('Silent SSO: Optional mediation also failed:', { name: errorName, message: errorMessage });
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
204
|
+
debug.log('Silent SSO: Silent mediation failed:', { name: errorName, message: errorMessage });
|
|
205
|
+
return null;
|
|
225
206
|
}
|
|
226
207
|
|
|
227
208
|
if (!credential || !credential.token) {
|
|
228
|
-
debug.log('Silent SSO: No credential returned (user
|
|
209
|
+
debug.log('Silent SSO: No credential returned (user not logged in at IdP or hasn\'t consented)');
|
|
229
210
|
return null;
|
|
230
211
|
}
|
|
231
212
|
|
|
@@ -392,9 +373,7 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
392
373
|
* @private
|
|
393
374
|
*/
|
|
394
375
|
public async exchangeIdTokenForSession(idToken: string): Promise<SessionLoginResponse> {
|
|
395
|
-
debug.log('
|
|
396
|
-
debug.log('exchangeIdTokenForSession: Token length:', idToken?.length);
|
|
397
|
-
debug.log('exchangeIdTokenForSession: Token preview:', idToken?.substring(0, 50) + '...');
|
|
376
|
+
debug.log('Exchanging ID token for session...');
|
|
398
377
|
|
|
399
378
|
try {
|
|
400
379
|
const response = await this.makeRequest<SessionLoginResponse>(
|
|
@@ -404,23 +383,14 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
404
383
|
{ cache: false }
|
|
405
384
|
);
|
|
406
385
|
|
|
407
|
-
debug.log('
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
hasUser: !!(response as any)?.user,
|
|
411
|
-
hasAccessToken: !!(response as any)?.accessToken,
|
|
412
|
-
userId: (response as any)?.user?.id,
|
|
413
|
-
username: (response as any)?.user?.username,
|
|
414
|
-
responseKeys: response ? Object.keys(response) : [],
|
|
386
|
+
debug.log('Token exchange complete:', {
|
|
387
|
+
hasSession: !!response?.sessionId,
|
|
388
|
+
hasUser: !!response?.user,
|
|
415
389
|
});
|
|
416
390
|
|
|
417
391
|
return response;
|
|
418
392
|
} catch (error) {
|
|
419
|
-
debug.error('
|
|
420
|
-
name: error instanceof Error ? error.name : 'Unknown',
|
|
421
|
-
message: error instanceof Error ? error.message : String(error),
|
|
422
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
423
|
-
});
|
|
393
|
+
debug.error('Token exchange failed:', error instanceof Error ? error.message : String(error));
|
|
424
394
|
throw error;
|
|
425
395
|
}
|
|
426
396
|
}
|
|
@@ -469,11 +439,15 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
|
|
|
469
439
|
* @private
|
|
470
440
|
*/
|
|
471
441
|
public generateNonce(): string {
|
|
472
|
-
if (typeof
|
|
473
|
-
return
|
|
442
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
443
|
+
return crypto.randomUUID();
|
|
444
|
+
}
|
|
445
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
446
|
+
const bytes = new Uint8Array(16);
|
|
447
|
+
crypto.getRandomValues(bytes);
|
|
448
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
474
449
|
}
|
|
475
|
-
|
|
476
|
-
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
450
|
+
throw new Error('No secure random source available for nonce generation');
|
|
477
451
|
}
|
|
478
452
|
|
|
479
453
|
/**
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils';
|
|
5
5
|
import type { LanguageMetadata } from '../utils/languageUtils';
|
|
6
6
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
7
|
+
import { isDev } from '../shared/utils/debugUtils';
|
|
7
8
|
|
|
8
9
|
export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
9
10
|
return class extends Base {
|
|
@@ -22,8 +23,10 @@ export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
22
23
|
|
|
23
24
|
if (isReactNative) {
|
|
24
25
|
try {
|
|
25
|
-
|
|
26
|
-
const
|
|
26
|
+
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
27
|
+
const moduleName = '@react-native-async-storage/async-storage';
|
|
28
|
+
const asyncStorageModule = await import(moduleName);
|
|
29
|
+
const storage = asyncStorageModule.default as unknown as { getItem: (key: string) => Promise<string | null>; setItem: (key: string, value: string) => Promise<void>; removeItem: (key: string) => Promise<void> };
|
|
27
30
|
return {
|
|
28
31
|
getItem: storage.getItem.bind(storage),
|
|
29
32
|
setItem: storage.setItem.bind(storage),
|
|
@@ -84,7 +87,7 @@ export function OxyServicesLanguageMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
84
87
|
|
|
85
88
|
return null;
|
|
86
89
|
} catch (error) {
|
|
87
|
-
if (
|
|
90
|
+
if (isDev()) {
|
|
88
91
|
console.warn('Failed to get current language:', error);
|
|
89
92
|
}
|
|
90
93
|
return null;
|
|
@@ -397,10 +397,15 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
|
|
|
397
397
|
* @private
|
|
398
398
|
*/
|
|
399
399
|
public generateState(): string {
|
|
400
|
-
if (typeof
|
|
401
|
-
return
|
|
400
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
401
|
+
return crypto.randomUUID();
|
|
402
402
|
}
|
|
403
|
-
|
|
403
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
404
|
+
const bytes = new Uint8Array(16);
|
|
405
|
+
crypto.getRandomValues(bytes);
|
|
406
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
407
|
+
}
|
|
408
|
+
throw new Error('No secure random source available for state generation');
|
|
404
409
|
}
|
|
405
410
|
|
|
406
411
|
/**
|
|
@@ -409,10 +414,15 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
|
|
|
409
414
|
* @private
|
|
410
415
|
*/
|
|
411
416
|
public generateNonce(): string {
|
|
412
|
-
if (typeof
|
|
413
|
-
return
|
|
417
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
418
|
+
return crypto.randomUUID();
|
|
419
|
+
}
|
|
420
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
421
|
+
const bytes = new Uint8Array(16);
|
|
422
|
+
crypto.getRandomValues(bytes);
|
|
423
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
414
424
|
}
|
|
415
|
-
|
|
425
|
+
throw new Error('No secure random source available for nonce generation');
|
|
416
426
|
}
|
|
417
427
|
|
|
418
428
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { BlockedUser, RestrictedUser } from '../models/interfaces';
|
|
5
5
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
6
|
+
import { isDev } from '../shared/utils/debugUtils';
|
|
6
7
|
|
|
7
8
|
export function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
8
9
|
return class extends Base {
|
|
@@ -35,7 +36,7 @@ export function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
35
36
|
});
|
|
36
37
|
} catch (error) {
|
|
37
38
|
// If there's an error, assume not in list to avoid breaking functionality
|
|
38
|
-
if (
|
|
39
|
+
if (isDev()) {
|
|
39
40
|
console.warn('Error checking user list:', error);
|
|
40
41
|
}
|
|
41
42
|
return false;
|
|
@@ -299,10 +299,15 @@ export function OxyServicesRedirectAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
299
299
|
* @private
|
|
300
300
|
*/
|
|
301
301
|
public generateState(): string {
|
|
302
|
-
if (typeof
|
|
303
|
-
return
|
|
302
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
303
|
+
return crypto.randomUUID();
|
|
304
304
|
}
|
|
305
|
-
|
|
305
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
306
|
+
const bytes = new Uint8Array(16);
|
|
307
|
+
crypto.getRandomValues(bytes);
|
|
308
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
309
|
+
}
|
|
310
|
+
throw new Error('No secure random source available for state generation');
|
|
306
311
|
}
|
|
307
312
|
|
|
308
313
|
/**
|
|
@@ -311,10 +316,15 @@ export function OxyServicesRedirectAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
311
316
|
* @private
|
|
312
317
|
*/
|
|
313
318
|
public generateNonce(): string {
|
|
314
|
-
if (typeof
|
|
315
|
-
return
|
|
319
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
320
|
+
return crypto.randomUUID();
|
|
321
|
+
}
|
|
322
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
323
|
+
const bytes = new Uint8Array(16);
|
|
324
|
+
crypto.getRandomValues(bytes);
|
|
325
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
316
326
|
}
|
|
317
|
-
|
|
327
|
+
throw new Error('No secure random source available for nonce generation');
|
|
318
328
|
}
|
|
319
329
|
|
|
320
330
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
5
5
|
import type { SecurityActivity, SecurityActivityResponse, SecurityEventType } from '../models/interfaces';
|
|
6
|
+
import { isDev } from '../shared/utils/debugUtils';
|
|
6
7
|
|
|
7
8
|
export function OxyServicesSecurityMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
8
9
|
return class extends Base {
|
|
@@ -71,7 +72,7 @@ export function OxyServicesSecurityMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
71
72
|
} catch (error) {
|
|
72
73
|
// Don't throw - logging failures shouldn't break user flow
|
|
73
74
|
// But log for monitoring
|
|
74
|
-
if (
|
|
75
|
+
if (isDev()) {
|
|
75
76
|
console.warn('[OxyServices] Failed to log private key exported event:', error);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
@@ -93,7 +94,7 @@ export function OxyServicesSecurityMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
93
94
|
} catch (error) {
|
|
94
95
|
// Don't throw - logging failures shouldn't break user flow
|
|
95
96
|
// But log for monitoring
|
|
96
|
-
if (
|
|
97
|
+
if (isDev()) {
|
|
97
98
|
console.warn('[OxyServices] Failed to log backup created event:', error);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for isDev() utility.
|
|
3
|
+
*
|
|
4
|
+
* These tests manipulate the global __DEV__ and process.env.NODE_ENV
|
|
5
|
+
* to verify isDev() works across RN, Node, and browser-like environments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
describe('isDev', () => {
|
|
9
|
+
const originalDev = (globalThis as any).__DEV__;
|
|
10
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
// Restore globals
|
|
14
|
+
if (originalDev === undefined) {
|
|
15
|
+
delete (globalThis as any).__DEV__;
|
|
16
|
+
} else {
|
|
17
|
+
(globalThis as any).__DEV__ = originalDev;
|
|
18
|
+
}
|
|
19
|
+
process.env.NODE_ENV = originalNodeEnv;
|
|
20
|
+
|
|
21
|
+
// Clear module cache so isDev re-evaluates
|
|
22
|
+
jest.resetModules();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
async function loadIsDev() {
|
|
26
|
+
const mod = await import('../debugUtils');
|
|
27
|
+
return mod.isDev;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
it('returns true when __DEV__ is true (React Native)', async () => {
|
|
31
|
+
(globalThis as any).__DEV__ = true;
|
|
32
|
+
const isDev = await loadIsDev();
|
|
33
|
+
expect(isDev()).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns false when __DEV__ is false', async () => {
|
|
37
|
+
(globalThis as any).__DEV__ = false;
|
|
38
|
+
const isDev = await loadIsDev();
|
|
39
|
+
expect(isDev()).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('falls back to NODE_ENV when __DEV__ is undefined', async () => {
|
|
43
|
+
delete (globalThis as any).__DEV__;
|
|
44
|
+
process.env.NODE_ENV = 'development';
|
|
45
|
+
const isDev = await loadIsDev();
|
|
46
|
+
expect(isDev()).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns false when NODE_ENV is production and __DEV__ is undefined', async () => {
|
|
50
|
+
delete (globalThis as any).__DEV__;
|
|
51
|
+
process.env.NODE_ENV = 'production';
|
|
52
|
+
const isDev = await loadIsDev();
|
|
53
|
+
expect(isDev()).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -14,7 +14,12 @@ declare const __DEV__: boolean | undefined;
|
|
|
14
14
|
* Check if running in development mode
|
|
15
15
|
*/
|
|
16
16
|
export const isDev = (): boolean => {
|
|
17
|
-
|
|
17
|
+
if (typeof __DEV__ !== 'undefined') return __DEV__;
|
|
18
|
+
try {
|
|
19
|
+
return typeof process !== 'undefined' && process.env?.NODE_ENV === 'development';
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
/**
|
|
@@ -42,8 +42,10 @@ export class DeviceManager {
|
|
|
42
42
|
}> {
|
|
43
43
|
if (this.isReactNative()) {
|
|
44
44
|
try {
|
|
45
|
-
|
|
46
|
-
const
|
|
45
|
+
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
46
|
+
const moduleName = '@react-native-async-storage/async-storage';
|
|
47
|
+
const asyncStorageModule = await import(moduleName);
|
|
48
|
+
const storage = asyncStorageModule.default as unknown as { getItem: (key: string) => Promise<string | null>; setItem: (key: string, value: string) => Promise<void>; removeItem: (key: string) => Promise<void> };
|
|
47
49
|
return {
|
|
48
50
|
getItem: storage.getItem.bind(storage),
|
|
49
51
|
setItem: storage.setItem.bind(storage),
|
|
@@ -168,15 +170,12 @@ export class DeviceManager {
|
|
|
168
170
|
* Generate a unique device ID
|
|
169
171
|
*/
|
|
170
172
|
private static generateDeviceId(): string {
|
|
171
|
-
// Use crypto.getRandomValues if available, otherwise fallback to Math.random
|
|
172
173
|
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
173
174
|
const array = new Uint8Array(32);
|
|
174
175
|
crypto.getRandomValues(array);
|
|
175
176
|
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
176
|
-
} else {
|
|
177
|
-
// Fallback for environments without crypto.getRandomValues
|
|
178
|
-
return 'device_' + Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
179
177
|
}
|
|
178
|
+
throw new Error('No secure random source available for device ID generation');
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
/**
|
package/src/utils/platform.ts
CHANGED
|
@@ -108,8 +108,9 @@ export async function initPlatformFromReactNative(): Promise<void> {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
try {
|
|
111
|
-
//
|
|
112
|
-
const
|
|
111
|
+
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
112
|
+
const moduleName = 'react-native';
|
|
113
|
+
const { Platform } = await import(moduleName);
|
|
113
114
|
setPlatformOS(Platform.OS as PlatformOS);
|
|
114
115
|
} catch {
|
|
115
116
|
// react-native not available, use detected platform
|