@oxyhq/core 1.0.2 → 1.1.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.
Files changed (114) hide show
  1. package/dist/cjs/AuthManager.js +19 -9
  2. package/dist/cjs/CrossDomainAuth.js +2 -2
  3. package/dist/cjs/HttpService.js +9 -8
  4. package/dist/cjs/OxyServices.base.js +16 -3
  5. package/dist/cjs/crypto/keyManager.js +29 -24
  6. package/dist/cjs/crypto/polyfill.js +6 -1
  7. package/dist/cjs/crypto/signatureService.js +40 -31
  8. package/dist/cjs/i18n/index.js +36 -45
  9. package/dist/cjs/i18n/locales/ar-SA.json +114 -115
  10. package/dist/cjs/i18n/locales/ca-ES.json +114 -115
  11. package/dist/cjs/i18n/locales/de-DE.json +114 -115
  12. package/dist/cjs/i18n/locales/en-US.json +936 -936
  13. package/dist/cjs/i18n/locales/es-ES.json +924 -924
  14. package/dist/cjs/i18n/locales/fr-FR.json +114 -115
  15. package/dist/cjs/i18n/locales/it-IT.json +114 -115
  16. package/dist/cjs/i18n/locales/ja-JP.json +1 -1
  17. package/dist/cjs/i18n/locales/ko-KR.json +114 -115
  18. package/dist/cjs/i18n/locales/locales/ar-SA.json +120 -0
  19. package/dist/cjs/i18n/locales/locales/ca-ES.json +120 -0
  20. package/dist/cjs/i18n/locales/locales/de-DE.json +120 -0
  21. package/dist/cjs/i18n/locales/locales/en-US.json +956 -0
  22. package/dist/cjs/i18n/locales/locales/es-ES.json +944 -0
  23. package/dist/cjs/i18n/locales/locales/fr-FR.json +120 -0
  24. package/dist/cjs/i18n/locales/locales/it-IT.json +120 -0
  25. package/dist/cjs/i18n/locales/locales/ja-JP.json +119 -0
  26. package/dist/cjs/i18n/locales/locales/ko-KR.json +120 -0
  27. package/dist/cjs/i18n/locales/locales/pt-PT.json +120 -0
  28. package/dist/cjs/i18n/locales/locales/zh-CN.json +120 -0
  29. package/dist/cjs/i18n/locales/pt-PT.json +114 -115
  30. package/dist/cjs/i18n/locales/zh-CN.json +114 -115
  31. package/dist/cjs/mixins/OxyServices.fedcm.js +13 -41
  32. package/dist/cjs/mixins/OxyServices.language.js +5 -2
  33. package/dist/cjs/mixins/OxyServices.privacy.js +2 -1
  34. package/dist/cjs/mixins/OxyServices.security.js +3 -2
  35. package/dist/cjs/shared/utils/debugUtils.js +8 -1
  36. package/dist/cjs/utils/deviceManager.js +3 -1
  37. package/dist/cjs/utils/platform.js +3 -2
  38. package/dist/esm/AuthManager.js +19 -9
  39. package/dist/esm/CrossDomainAuth.js +2 -2
  40. package/dist/esm/HttpService.js +9 -8
  41. package/dist/esm/OxyServices.base.js +16 -3
  42. package/dist/esm/crypto/keyManager.js +29 -24
  43. package/dist/esm/crypto/polyfill.js +6 -1
  44. package/dist/esm/crypto/signatureService.js +40 -31
  45. package/dist/esm/i18n/index.js +11 -23
  46. package/dist/esm/i18n/locales/ar-SA.json +114 -115
  47. package/dist/esm/i18n/locales/ca-ES.json +114 -115
  48. package/dist/esm/i18n/locales/de-DE.json +114 -115
  49. package/dist/esm/i18n/locales/en-US.json +936 -936
  50. package/dist/esm/i18n/locales/es-ES.json +924 -924
  51. package/dist/esm/i18n/locales/fr-FR.json +114 -115
  52. package/dist/esm/i18n/locales/it-IT.json +114 -115
  53. package/dist/esm/i18n/locales/ja-JP.json +1 -1
  54. package/dist/esm/i18n/locales/ko-KR.json +114 -115
  55. package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
  56. package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
  57. package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
  58. package/dist/esm/i18n/locales/locales/en-US.json +956 -0
  59. package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
  60. package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
  61. package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
  62. package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
  63. package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
  64. package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
  65. package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
  66. package/dist/esm/i18n/locales/pt-PT.json +114 -115
  67. package/dist/esm/i18n/locales/zh-CN.json +114 -115
  68. package/dist/esm/mixins/OxyServices.fedcm.js +13 -41
  69. package/dist/esm/mixins/OxyServices.language.js +5 -2
  70. package/dist/esm/mixins/OxyServices.privacy.js +2 -1
  71. package/dist/esm/mixins/OxyServices.security.js +3 -2
  72. package/dist/esm/shared/utils/debugUtils.js +8 -1
  73. package/dist/esm/utils/deviceManager.js +3 -1
  74. package/dist/esm/utils/platform.js +3 -2
  75. package/dist/types/CrossDomainAuth.d.ts +2 -2
  76. package/dist/types/OxyServices.base.d.ts +4 -1
  77. package/dist/types/OxyServices.d.ts +13 -0
  78. package/dist/types/index.d.ts +3 -0
  79. package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
  80. package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
  81. package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
  82. package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
  83. package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
  84. package/dist/types/mixins/OxyServices.features.d.ts +2 -0
  85. package/dist/types/mixins/OxyServices.fedcm.d.ts +4 -2
  86. package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
  87. package/dist/types/mixins/OxyServices.language.d.ts +2 -0
  88. package/dist/types/mixins/OxyServices.location.d.ts +2 -0
  89. package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
  90. package/dist/types/mixins/OxyServices.popup.d.ts +2 -0
  91. package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
  92. package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
  93. package/dist/types/mixins/OxyServices.security.d.ts +2 -0
  94. package/dist/types/mixins/OxyServices.user.d.ts +2 -0
  95. package/dist/types/mixins/OxyServices.utility.d.ts +2 -0
  96. package/package.json +1 -2
  97. package/src/AuthManager.ts +25 -15
  98. package/src/CrossDomainAuth.ts +2 -2
  99. package/src/HttpService.ts +9 -8
  100. package/src/OxyServices.base.ts +21 -4
  101. package/src/OxyServices.ts +23 -2
  102. package/src/crypto/keyManager.ts +30 -25
  103. package/src/crypto/polyfill.ts +6 -1
  104. package/src/crypto/signatureService.ts +43 -37
  105. package/src/i18n/index.ts +33 -45
  106. package/src/index.ts +3 -0
  107. package/src/mixins/OxyServices.fedcm.ts +14 -44
  108. package/src/mixins/OxyServices.language.ts +6 -3
  109. package/src/mixins/OxyServices.privacy.ts +2 -1
  110. package/src/mixins/OxyServices.security.ts +3 -2
  111. package/src/shared/utils/__tests__/debugUtils.test.ts +55 -0
  112. package/src/shared/utils/debugUtils.ts +6 -1
  113. package/src/utils/deviceManager.ts +4 -2
  114. package/src/utils/platform.ts +3 -2
@@ -147,7 +147,10 @@ export function OxyServicesFedCMMixin(Base) {
147
147
  }
148
148
  const clientId = this.getClientId();
149
149
  debug.log('Silent SSO: Starting for', clientId);
150
- // First try silent mediation (no UI) - works if user previously consented
150
+ // Only try silent mediation (no UI) - works if user previously consented.
151
+ // We intentionally do NOT fall back to optional mediation here because
152
+ // this runs on app startup — showing browser UI without user action is bad UX.
153
+ // Optional/interactive mediation should only happen when the user clicks "Sign In".
151
154
  let credential = null;
152
155
  try {
153
156
  const nonce = this.generateNonce();
@@ -161,33 +164,13 @@ export function OxyServicesFedCMMixin(Base) {
161
164
  debug.log('Silent SSO: Silent mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
162
165
  }
163
166
  catch (silentError) {
164
- // Silent mediation failed - this is expected if user hasn't consented before or is in quiet period
165
167
  const errorName = silentError instanceof Error ? silentError.name : 'Unknown';
166
168
  const errorMessage = silentError instanceof Error ? silentError.message : String(silentError);
167
- debug.log('Silent SSO: Silent mediation error (will try optional):', { name: errorName, message: errorMessage });
168
- }
169
- // If silent failed, try optional mediation which shows browser UI if needed
170
- if (!credential || !credential.token) {
171
- try {
172
- const nonce = this.generateNonce();
173
- debug.log('Silent SSO: Trying optional mediation (may show browser UI)...');
174
- credential = await this.requestIdentityCredential({
175
- configURL: this.constructor.DEFAULT_CONFIG_URL,
176
- clientId,
177
- nonce,
178
- mediation: 'optional',
179
- });
180
- debug.log('Silent SSO: Optional mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
181
- }
182
- catch (optionalError) {
183
- const errorName = optionalError instanceof Error ? optionalError.name : 'Unknown';
184
- const errorMessage = optionalError instanceof Error ? optionalError.message : String(optionalError);
185
- debug.log('Silent SSO: Optional mediation also failed:', { name: errorName, message: errorMessage });
186
- return null;
187
- }
169
+ debug.log('Silent SSO: Silent mediation failed:', { name: errorName, message: errorMessage });
170
+ return null;
188
171
  }
189
172
  if (!credential || !credential.token) {
190
- debug.log('Silent SSO: No credential returned (user may have dismissed prompt or is not logged in at IdP)');
173
+ debug.log('Silent SSO: No credential returned (user not logged in at IdP or hasn\'t consented)');
191
174
  return null;
192
175
  }
193
176
  debug.log('Silent SSO: Got credential, exchanging for session...');
@@ -337,28 +320,17 @@ export function OxyServicesFedCMMixin(Base) {
337
320
  * @private
338
321
  */
339
322
  async exchangeIdTokenForSession(idToken) {
340
- debug.log('exchangeIdTokenForSession: Starting exchange...');
341
- debug.log('exchangeIdTokenForSession: Token length:', idToken?.length);
342
- debug.log('exchangeIdTokenForSession: Token preview:', idToken?.substring(0, 50) + '...');
323
+ debug.log('Exchanging ID token for session...');
343
324
  try {
344
325
  const response = await this.makeRequest('POST', '/api/fedcm/exchange', { id_token: idToken }, { cache: false });
345
- debug.log('exchangeIdTokenForSession: Response received:', {
346
- hasResponse: !!response,
347
- hasSessionId: !!response?.sessionId,
326
+ debug.log('Token exchange complete:', {
327
+ hasSession: !!response?.sessionId,
348
328
  hasUser: !!response?.user,
349
- hasAccessToken: !!response?.accessToken,
350
- userId: response?.user?.id,
351
- username: response?.user?.username,
352
- responseKeys: response ? Object.keys(response) : [],
353
329
  });
354
330
  return response;
355
331
  }
356
332
  catch (error) {
357
- debug.error('exchangeIdTokenForSession: Error:', {
358
- name: error instanceof Error ? error.name : 'Unknown',
359
- message: error instanceof Error ? error.message : String(error),
360
- stack: error instanceof Error ? error.stack : undefined,
361
- });
333
+ debug.error('Token exchange failed:', error instanceof Error ? error.message : String(error));
362
334
  throw error;
363
335
  }
364
336
  }
@@ -423,9 +395,9 @@ export function OxyServicesFedCMMixin(Base) {
423
395
  }
424
396
  },
425
397
  _a.DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json',
426
- _a.FEDCM_TIMEOUT = 60000 // 1 minute for interactive
398
+ _a.FEDCM_TIMEOUT = 15000 // 15 seconds for interactive
427
399
  ,
428
- _a.FEDCM_SILENT_TIMEOUT = 10000 // 10 seconds for silent mediation
400
+ _a.FEDCM_SILENT_TIMEOUT = 3000 // 3 seconds for silent mediation
429
401
  ,
430
402
  _a;
431
403
  }
@@ -2,6 +2,7 @@
2
2
  * Language Methods Mixin
3
3
  */
4
4
  import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils';
5
+ import { isDev } from '../shared/utils/debugUtils';
5
6
  export function OxyServicesLanguageMixin(Base) {
6
7
  return class extends Base {
7
8
  constructor(...args) {
@@ -14,7 +15,9 @@ export function OxyServicesLanguageMixin(Base) {
14
15
  const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
15
16
  if (isReactNative) {
16
17
  try {
17
- const asyncStorageModule = await import('@react-native-async-storage/async-storage');
18
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
19
+ const moduleName = '@react-native-async-storage/async-storage';
20
+ const asyncStorageModule = await import(moduleName);
18
21
  const storage = asyncStorageModule.default;
19
22
  return {
20
23
  getItem: storage.getItem.bind(storage),
@@ -77,7 +80,7 @@ export function OxyServicesLanguageMixin(Base) {
77
80
  return null;
78
81
  }
79
82
  catch (error) {
80
- if (__DEV__) {
83
+ if (isDev()) {
81
84
  console.warn('Failed to get current language:', error);
82
85
  }
83
86
  return null;
@@ -1,3 +1,4 @@
1
+ import { isDev } from '../shared/utils/debugUtils';
1
2
  export function OxyServicesPrivacyMixin(Base) {
2
3
  return class extends Base {
3
4
  constructor(...args) {
@@ -25,7 +26,7 @@ export function OxyServicesPrivacyMixin(Base) {
25
26
  }
26
27
  catch (error) {
27
28
  // If there's an error, assume not in list to avoid breaking functionality
28
- if (__DEV__) {
29
+ if (isDev()) {
29
30
  console.warn('Error checking user list:', error);
30
31
  }
31
32
  return false;
@@ -1,3 +1,4 @@
1
+ import { isDev } from '../shared/utils/debugUtils';
1
2
  export function OxyServicesSecurityMixin(Base) {
2
3
  return class extends Base {
3
4
  constructor(...args) {
@@ -52,7 +53,7 @@ export function OxyServicesSecurityMixin(Base) {
52
53
  catch (error) {
53
54
  // Don't throw - logging failures shouldn't break user flow
54
55
  // But log for monitoring
55
- if (__DEV__) {
56
+ if (isDev()) {
56
57
  console.warn('[OxyServices] Failed to log private key exported event:', error);
57
58
  }
58
59
  }
@@ -69,7 +70,7 @@ export function OxyServicesSecurityMixin(Base) {
69
70
  catch (error) {
70
71
  // Don't throw - logging failures shouldn't break user flow
71
72
  // But log for monitoring
72
- if (__DEV__) {
73
+ if (isDev()) {
73
74
  console.warn('[OxyServices] Failed to log backup created event:', error);
74
75
  }
75
76
  }
@@ -10,7 +10,14 @@
10
10
  * Check if running in development mode
11
11
  */
12
12
  export const isDev = () => {
13
- return typeof __DEV__ !== 'undefined' && __DEV__;
13
+ if (typeof __DEV__ !== 'undefined')
14
+ return __DEV__;
15
+ try {
16
+ return typeof process !== 'undefined' && process.env?.NODE_ENV === 'development';
17
+ }
18
+ catch {
19
+ return false;
20
+ }
14
21
  };
15
22
  /**
16
23
  * Log a debug message (only in development)
@@ -15,7 +15,9 @@ export class DeviceManager {
15
15
  static async getStorage() {
16
16
  if (this.isReactNative()) {
17
17
  try {
18
- const asyncStorageModule = await import('@react-native-async-storage/async-storage');
18
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
19
+ const moduleName = '@react-native-async-storage/async-storage';
20
+ const asyncStorageModule = await import(moduleName);
19
21
  const storage = asyncStorageModule.default;
20
22
  return {
21
23
  getItem: storage.getItem.bind(storage),
@@ -92,8 +92,9 @@ export async function initPlatformFromReactNative() {
92
92
  return; // Already initialized
93
93
  }
94
94
  try {
95
- // Dynamic import to avoid bundler issues
96
- const { Platform } = await import('react-native');
95
+ // Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
96
+ const moduleName = 'react-native';
97
+ const { Platform } = await import(moduleName);
97
98
  setPlatformOS(Platform.OS);
98
99
  }
99
100
  catch {
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Usage:
12
12
  * ```typescript
13
- * import { CrossDomainAuth } from '@oxyhq/services';
13
+ * import { CrossDomainAuth } from '@oxyhq/core';
14
14
  *
15
15
  * const auth = new CrossDomainAuth(oxyServices);
16
16
  *
@@ -142,7 +142,7 @@ export declare class CrossDomainAuth {
142
142
  *
143
143
  * @example
144
144
  * ```typescript
145
- * import { createCrossDomainAuth } from '@oxyhq/services';
145
+ * import { createCrossDomainAuth } from '@oxyhq/core';
146
146
  *
147
147
  * const oxyServices = new OxyServices({ baseURL: 'https://api.oxy.so' });
148
148
  * const auth = createCrossDomainAuth(oxyServices);
@@ -66,8 +66,11 @@ export declare class OxyServicesBase {
66
66
  * Clear stored authentication tokens
67
67
  */
68
68
  clearTokens(): void;
69
+ /** @internal */ _cachedUserId: string | null | undefined;
70
+ /** @internal */ _cachedAccessToken: string | null;
69
71
  /**
70
- * Get the current user ID from the access token
72
+ * Get the current user ID from the access token.
73
+ * Caches the decoded value and invalidates when the token changes.
71
74
  */
72
75
  getCurrentUserId(): string | null;
73
76
  /**
@@ -58,12 +58,25 @@
58
58
  */
59
59
  import { type OxyConfig } from './OxyServices.base';
60
60
  import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors';
61
+ import type { SessionLoginResponse } from './models/session';
62
+ import type { FedCMAuthOptions, FedCMConfig } from './mixins/OxyServices.fedcm';
63
+ import type { PopupAuthOptions } from './mixins/OxyServices.popup';
64
+ import type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
61
65
  import { composeOxyServices } from './mixins';
62
66
  declare const OxyServices_base: any;
63
67
  export declare class OxyServices extends OxyServices_base {
64
68
  constructor(config: OxyConfig);
65
69
  }
66
70
  export interface OxyServices extends InstanceType<ReturnType<typeof composeOxyServices>> {
71
+ isFedCMSupported(): boolean;
72
+ signInWithFedCM(options?: FedCMAuthOptions): Promise<SessionLoginResponse>;
73
+ silentSignInWithFedCM(): Promise<SessionLoginResponse | null>;
74
+ revokeFedCMCredential(): Promise<void>;
75
+ getFedCMConfig(): FedCMConfig;
76
+ signInWithPopup(options?: PopupAuthOptions): Promise<SessionLoginResponse>;
77
+ signUpWithPopup(options?: PopupAuthOptions): Promise<SessionLoginResponse>;
78
+ signInWithRedirect(options?: RedirectAuthOptions): void;
79
+ signUpWithRedirect(options?: RedirectAuthOptions): void;
67
80
  }
68
81
  export { OxyAuthenticationError, OxyAuthenticationTimeoutError };
69
82
  /**
@@ -20,6 +20,9 @@ export { AuthManager, createAuthManager } from './AuthManager';
20
20
  export type { StorageAdapter, AuthStateChangeCallback, AuthMethod, AuthManagerConfig } from './AuthManager';
21
21
  export { CrossDomainAuth, createCrossDomainAuth } from './CrossDomainAuth';
22
22
  export type { CrossDomainAuthOptions } from './CrossDomainAuth';
23
+ export type { FedCMAuthOptions, FedCMConfig } from './mixins/OxyServices.fedcm';
24
+ export type { PopupAuthOptions } from './mixins/OxyServices.popup';
25
+ export type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
23
26
  export { KeyManager, SignatureService, RecoveryPhraseService } from './crypto';
24
27
  export type { KeyPair, SignedMessage, AuthChallenge, RecoveryPhraseResult } from './crypto';
25
28
  export * from './models/interfaces';
@@ -44,6 +44,8 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
44
44
  getCloudURL(): string;
45
45
  setTokens(accessToken: string, refreshToken?: string): void;
46
46
  clearTokens(): void;
47
+ _cachedUserId: string | null | undefined;
48
+ _cachedAccessToken: string | null;
47
49
  getCurrentUserId(): string | null;
48
50
  hasValidToken(): boolean;
49
51
  getAccessToken(): string | null;
@@ -113,6 +113,8 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
113
113
  getCloudURL(): string;
114
114
  setTokens(accessToken: string, refreshToken?: string): void;
115
115
  clearTokens(): void;
116
+ _cachedUserId: string | null | undefined;
117
+ _cachedAccessToken: string | null;
116
118
  getCurrentUserId(): string | null;
117
119
  hasValidToken(): boolean;
118
120
  getAccessToken(): string | null;
@@ -164,6 +164,8 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
164
164
  getCloudURL(): string;
165
165
  setTokens(accessToken: string, refreshToken?: string): void;
166
166
  clearTokens(): void;
167
+ _cachedUserId: string | null | undefined;
168
+ _cachedAccessToken: string | null;
167
169
  getCurrentUserId(): string | null;
168
170
  hasValidToken(): boolean;
169
171
  getAccessToken(): string | null;
@@ -77,6 +77,8 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
77
77
  getCloudURL(): string;
78
78
  setTokens(accessToken: string, refreshToken?: string): void;
79
79
  clearTokens(): void;
80
+ _cachedUserId: string | null | undefined;
81
+ _cachedAccessToken: string | null;
80
82
  getCurrentUserId(): string | null;
81
83
  hasValidToken(): boolean;
82
84
  getAccessToken(): string | null;
@@ -74,6 +74,8 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
74
74
  getCloudURL(): string;
75
75
  setTokens(accessToken: string, refreshToken?: string): void;
76
76
  clearTokens(): void;
77
+ _cachedUserId: string | null | undefined;
78
+ _cachedAccessToken: string | null;
77
79
  getCurrentUserId(): string | null;
78
80
  hasValidToken(): boolean;
79
81
  getAccessToken(): string | null;
@@ -206,6 +206,8 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
206
206
  getCloudURL(): string;
207
207
  setTokens(accessToken: string, refreshToken?: string): void;
208
208
  clearTokens(): void;
209
+ _cachedUserId: string | null | undefined;
210
+ _cachedAccessToken: string | null;
209
211
  getCurrentUserId(): string | null;
210
212
  hasValidToken(): boolean;
211
213
  getAccessToken(): string | null;
@@ -170,6 +170,8 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
170
170
  getCloudURL(): string;
171
171
  setTokens(accessToken: string, refreshToken?: string): void;
172
172
  clearTokens(): void;
173
+ _cachedUserId: string | null | undefined;
174
+ _cachedAccessToken: string | null;
173
175
  getCurrentUserId(): string | null;
174
176
  hasValidToken(): boolean;
175
177
  getAccessToken(): string | null;
@@ -189,8 +191,8 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
189
191
  }>;
190
192
  };
191
193
  readonly DEFAULT_CONFIG_URL: "https://auth.oxy.so/fedcm.json";
192
- readonly FEDCM_TIMEOUT: 60000;
193
- readonly FEDCM_SILENT_TIMEOUT: 10000;
194
+ readonly FEDCM_TIMEOUT: 15000;
195
+ readonly FEDCM_SILENT_TIMEOUT: 3000;
194
196
  /**
195
197
  * Check if FedCM is supported in the current browser
196
198
  */
@@ -63,6 +63,8 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
63
63
  getCloudURL(): string;
64
64
  setTokens(accessToken: string, refreshToken?: string): void;
65
65
  clearTokens(): void;
66
+ _cachedUserId: string | null | undefined;
67
+ _cachedAccessToken: string | null;
66
68
  getCurrentUserId(): string | null;
67
69
  hasValidToken(): boolean;
68
70
  getAccessToken(): string | null;
@@ -59,6 +59,8 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
59
59
  getCloudURL(): string;
60
60
  setTokens(accessToken: string, refreshToken?: string): void;
61
61
  clearTokens(): void;
62
+ _cachedUserId: string | null | undefined;
63
+ _cachedAccessToken: string | null;
62
64
  getCurrentUserId(): string | null;
63
65
  hasValidToken(): boolean;
64
66
  getAccessToken(): string | null;
@@ -42,6 +42,8 @@ export declare function OxyServicesLocationMixin<T extends typeof OxyServicesBas
42
42
  getCloudURL(): string;
43
43
  setTokens(accessToken: string, refreshToken?: string): void;
44
44
  clearTokens(): void;
45
+ _cachedUserId: string | null | undefined;
46
+ _cachedAccessToken: string | null;
45
47
  getCurrentUserId(): string | null;
46
48
  hasValidToken(): boolean;
47
49
  getAccessToken(): string | null;
@@ -89,6 +89,8 @@ export declare function OxyServicesPaymentMixin<T extends typeof OxyServicesBase
89
89
  getCloudURL(): string;
90
90
  setTokens(accessToken: string, refreshToken?: string): void;
91
91
  clearTokens(): void;
92
+ _cachedUserId: string | null | undefined;
93
+ _cachedAccessToken: string | null;
92
94
  getCurrentUserId(): string | null;
93
95
  hasValidToken(): boolean;
94
96
  getAccessToken(): string | null;
@@ -177,6 +177,8 @@ export declare function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBa
177
177
  getCloudURL(): string;
178
178
  setTokens(accessToken: string, refreshToken?: string): void;
179
179
  clearTokens(): void;
180
+ _cachedUserId: string | null | undefined;
181
+ _cachedAccessToken: string | null;
180
182
  getCurrentUserId(): string | null;
181
183
  hasValidToken(): boolean;
182
184
  getAccessToken(): string | null;
@@ -100,6 +100,8 @@ export declare function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase
100
100
  getCloudURL(): string;
101
101
  setTokens(accessToken: string, refreshToken?: string): void;
102
102
  clearTokens(): void;
103
+ _cachedUserId: string | null | undefined;
104
+ _cachedAccessToken: string | null;
103
105
  getCurrentUserId(): string | null;
104
106
  hasValidToken(): boolean;
105
107
  getAccessToken(): string | null;
@@ -216,6 +216,8 @@ export declare function OxyServicesRedirectAuthMixin<T extends typeof OxyService
216
216
  getCloudURL(): string;
217
217
  setTokens(accessToken: string, refreshToken?: string): void;
218
218
  clearTokens(): void;
219
+ _cachedUserId: string | null | undefined;
220
+ _cachedAccessToken: string | null;
219
221
  getCurrentUserId(): string | null;
220
222
  hasValidToken(): boolean;
221
223
  getAccessToken(): string | null;
@@ -56,6 +56,8 @@ export declare function OxyServicesSecurityMixin<T extends typeof OxyServicesBas
56
56
  getCloudURL(): string;
57
57
  setTokens(accessToken: string, refreshToken?: string): void;
58
58
  clearTokens(): void;
59
+ _cachedUserId: string | null | undefined;
60
+ _cachedAccessToken: string | null;
59
61
  getCurrentUserId(): string | null;
60
62
  hasValidToken(): boolean;
61
63
  getAccessToken(): string | null;
@@ -160,6 +160,8 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
160
160
  getCloudURL(): string;
161
161
  setTokens(accessToken: string, refreshToken?: string): void;
162
162
  clearTokens(): void;
163
+ _cachedUserId: string | null | undefined;
164
+ _cachedAccessToken: string | null;
163
165
  getCurrentUserId(): string | null;
164
166
  hasValidToken(): boolean;
165
167
  getAccessToken(): string | null;
@@ -71,6 +71,8 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
71
71
  getCloudURL(): string;
72
72
  setTokens(accessToken: string, refreshToken?: string): void;
73
73
  clearTokens(): void;
74
+ _cachedUserId: string | null | undefined;
75
+ _cachedAccessToken: string | null;
74
76
  getCurrentUserId(): string | null;
75
77
  hasValidToken(): boolean;
76
78
  getAccessToken(): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -65,7 +65,6 @@
65
65
  "lint": "biome lint --error-on-warnings ./src"
66
66
  },
67
67
  "dependencies": {
68
- "axios": "^1.9.0",
69
68
  "bip39": "^3.1.0",
70
69
  "buffer": "^6.0.3",
71
70
  "elliptic": "^6.6.1",
@@ -10,13 +10,13 @@
10
10
  import type { OxyServices } from './OxyServices';
11
11
  import type { HttpService } from './HttpService';
12
12
  import type { SessionLoginResponse, MinimalUserData } from './models/session';
13
+ import { retryAsync } from './utils/asyncUtils';
13
14
 
14
15
  /**
15
- * OxyServices with optional FedCM methods (provided by FedCM mixin).
16
+ * OxyServices already declares revokeFedCMCredential via mixin type augmentation.
17
+ * This alias is kept for readability in the signOut method.
16
18
  */
17
- interface OxyServicesWithFedCM extends OxyServices {
18
- revokeFedCMCredential?: () => Promise<void>;
19
- }
19
+ type OxyServicesWithFedCM = OxyServices;
20
20
 
21
21
  /**
22
22
  * Storage adapter interface for platform-agnostic storage.
@@ -270,19 +270,29 @@ export class AuthManager {
270
270
  }
271
271
 
272
272
  try {
273
- // Cast httpService to proper type (needed due to mixin composition)
274
- const httpService = this.oxyServices.httpService as HttpService;
275
- const response = await httpService.request<SessionLoginResponse>({
276
- method: 'POST',
277
- url: '/api/auth/refresh',
278
- data: { refreshToken },
279
- cache: false,
280
- });
281
-
282
- await this.handleAuthSuccess(response, 'credentials');
273
+ await retryAsync(
274
+ async () => {
275
+ const httpService = this.oxyServices.httpService as HttpService;
276
+ const response = await httpService.request<SessionLoginResponse>({
277
+ method: 'POST',
278
+ url: '/api/auth/refresh',
279
+ data: { refreshToken },
280
+ cache: false,
281
+ });
282
+ await this.handleAuthSuccess(response, 'credentials');
283
+ },
284
+ 2, // 2 retries = 3 total attempts
285
+ 1000, // 1s base delay with exponential backoff + jitter
286
+ (error: any) => {
287
+ // Don't retry on 4xx client errors (invalid/revoked token)
288
+ const status = error?.status ?? error?.response?.status;
289
+ if (status && status >= 400 && status < 500) return false;
290
+ return true;
291
+ }
292
+ );
283
293
  return true;
284
294
  } catch {
285
- // Refresh failed, clear session and update state
295
+ // All retry attempts exhausted, clear session
286
296
  await this.clearSession();
287
297
  this.currentUser = null;
288
298
  this.notifyListeners();
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Usage:
12
12
  * ```typescript
13
- * import { CrossDomainAuth } from '@oxyhq/services';
13
+ * import { CrossDomainAuth } from '@oxyhq/core';
14
14
  *
15
15
  * const auth = new CrossDomainAuth(oxyServices);
16
16
  *
@@ -287,7 +287,7 @@ export class CrossDomainAuth {
287
287
  *
288
288
  * @example
289
289
  * ```typescript
290
- * import { createCrossDomainAuth } from '@oxyhq/services';
290
+ * import { createCrossDomainAuth } from '@oxyhq/core';
291
291
  *
292
292
  * const oxyServices = new OxyServices({ baseURL: 'https://api.oxy.so' });
293
293
  * const auth = createCrossDomainAuth(oxyServices);
@@ -17,6 +17,7 @@ import { TTLCache, registerCacheForCleanup } from './utils/cache';
17
17
  import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/requestUtils';
18
18
  import { retryAsync } from './utils/asyncUtils';
19
19
  import { handleHttpError } from './utils/errorUtils';
20
+ import { isDev } from './shared/utils/debugUtils';
20
21
  import { jwtDecode } from 'jwt-decode';
21
22
  import { isNative, getPlatformOS } from './utils/platform';
22
23
  import type { OxyConfig } from './models/interfaces';
@@ -279,7 +280,7 @@ export class HttpService {
279
280
  }
280
281
 
281
282
  // Debug logging for CSRF issues
282
- if (isStateChangingMethod && __DEV__) {
283
+ if (isStateChangingMethod && isDev()) {
283
284
  console.log('[HttpService] CSRF Debug:', {
284
285
  url,
285
286
  method,
@@ -484,20 +485,20 @@ export class HttpService {
484
485
  // Return cached token if available
485
486
  const cachedToken = this.tokenStore.getCsrfToken();
486
487
  if (cachedToken) {
487
- if (__DEV__) console.log('[HttpService] Using cached CSRF token');
488
+ if (isDev()) console.log('[HttpService] Using cached CSRF token');
488
489
  return cachedToken;
489
490
  }
490
491
 
491
492
  // Deduplicate concurrent CSRF token fetches
492
493
  const existingPromise = this.tokenStore.getCsrfTokenFetchPromise();
493
494
  if (existingPromise) {
494
- if (__DEV__) console.log('[HttpService] Waiting for existing CSRF fetch');
495
+ if (isDev()) console.log('[HttpService] Waiting for existing CSRF fetch');
495
496
  return existingPromise;
496
497
  }
497
498
 
498
499
  const fetchPromise = (async () => {
499
500
  try {
500
- if (__DEV__) console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/api/csrf-token`);
501
+ if (isDev()) console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/api/csrf-token`);
501
502
 
502
503
  // Use AbortController for timeout (more compatible than AbortSignal.timeout)
503
504
  const controller = new AbortController();
@@ -512,11 +513,11 @@ export class HttpService {
512
513
 
513
514
  clearTimeout(timeoutId);
514
515
 
515
- if (__DEV__) console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
516
+ if (isDev()) console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
516
517
 
517
518
  if (response.ok) {
518
519
  const data = await response.json() as { csrfToken?: string };
519
- if (__DEV__) console.log('[HttpService] CSRF response data:', data);
520
+ if (isDev()) console.log('[HttpService] CSRF response data:', data);
520
521
  const token = data.csrfToken || null;
521
522
  this.tokenStore.setCsrfToken(token);
522
523
  this.logger.debug('CSRF token fetched');
@@ -531,11 +532,11 @@ export class HttpService {
531
532
  return headerToken;
532
533
  }
533
534
 
534
- if (__DEV__) console.log('[HttpService] CSRF fetch failed with status:', response.status);
535
+ if (isDev()) console.log('[HttpService] CSRF fetch failed with status:', response.status);
535
536
  this.logger.warn('Failed to fetch CSRF token:', response.status);
536
537
  return null;
537
538
  } catch (error) {
538
- if (__DEV__) console.log('[HttpService] CSRF fetch error:', error);
539
+ if (isDev()) console.log('[HttpService] CSRF fetch error:', error);
539
540
  this.logger.warn('CSRF token fetch error:', error);
540
541
  return null;
541
542
  } finally {