@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.
Files changed (277) hide show
  1. package/README.md +50 -0
  2. package/dist/cjs/AuthManager.js +361 -0
  3. package/dist/cjs/CrossDomainAuth.js +258 -0
  4. package/dist/cjs/HttpService.js +618 -0
  5. package/dist/cjs/OxyServices.base.js +263 -0
  6. package/dist/cjs/OxyServices.errors.js +22 -0
  7. package/dist/cjs/OxyServices.js +63 -0
  8. package/dist/cjs/constants/version.js +16 -0
  9. package/dist/cjs/crypto/index.js +20 -0
  10. package/dist/cjs/crypto/keyManager.js +887 -0
  11. package/dist/cjs/crypto/polyfill.js +64 -0
  12. package/dist/cjs/crypto/recoveryPhrase.js +169 -0
  13. package/dist/cjs/crypto/signatureService.js +296 -0
  14. package/dist/cjs/i18n/index.js +73 -0
  15. package/dist/cjs/i18n/locales/ar-SA.json +120 -0
  16. package/dist/cjs/i18n/locales/ca-ES.json +120 -0
  17. package/dist/cjs/i18n/locales/de-DE.json +120 -0
  18. package/dist/cjs/i18n/locales/en-US.json +956 -0
  19. package/dist/cjs/i18n/locales/es-ES.json +944 -0
  20. package/dist/cjs/i18n/locales/fr-FR.json +120 -0
  21. package/dist/cjs/i18n/locales/it-IT.json +120 -0
  22. package/dist/cjs/i18n/locales/ja-JP.json +119 -0
  23. package/dist/cjs/i18n/locales/ko-KR.json +120 -0
  24. package/dist/cjs/i18n/locales/locales/ar-SA.json +120 -0
  25. package/dist/cjs/i18n/locales/locales/ca-ES.json +120 -0
  26. package/dist/cjs/i18n/locales/locales/de-DE.json +120 -0
  27. package/dist/cjs/i18n/locales/locales/en-US.json +956 -0
  28. package/dist/cjs/i18n/locales/locales/es-ES.json +944 -0
  29. package/dist/cjs/i18n/locales/locales/fr-FR.json +120 -0
  30. package/dist/cjs/i18n/locales/locales/it-IT.json +120 -0
  31. package/dist/cjs/i18n/locales/locales/ja-JP.json +119 -0
  32. package/dist/cjs/i18n/locales/locales/ko-KR.json +120 -0
  33. package/dist/cjs/i18n/locales/locales/pt-PT.json +120 -0
  34. package/dist/cjs/i18n/locales/locales/zh-CN.json +120 -0
  35. package/dist/cjs/i18n/locales/pt-PT.json +120 -0
  36. package/dist/cjs/i18n/locales/zh-CN.json +120 -0
  37. package/dist/cjs/index.js +153 -0
  38. package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
  39. package/dist/cjs/mixins/OxyServices.assets.js +380 -0
  40. package/dist/cjs/mixins/OxyServices.auth.js +259 -0
  41. package/dist/cjs/mixins/OxyServices.developer.js +97 -0
  42. package/dist/cjs/mixins/OxyServices.devices.js +116 -0
  43. package/dist/cjs/mixins/OxyServices.features.js +309 -0
  44. package/dist/cjs/mixins/OxyServices.fedcm.js +435 -0
  45. package/dist/cjs/mixins/OxyServices.karma.js +108 -0
  46. package/dist/cjs/mixins/OxyServices.language.js +154 -0
  47. package/dist/cjs/mixins/OxyServices.location.js +43 -0
  48. package/dist/cjs/mixins/OxyServices.payment.js +158 -0
  49. package/dist/cjs/mixins/OxyServices.popup.js +371 -0
  50. package/dist/cjs/mixins/OxyServices.privacy.js +162 -0
  51. package/dist/cjs/mixins/OxyServices.redirect.js +345 -0
  52. package/dist/cjs/mixins/OxyServices.security.js +81 -0
  53. package/dist/cjs/mixins/OxyServices.user.js +355 -0
  54. package/dist/cjs/mixins/OxyServices.utility.js +156 -0
  55. package/dist/cjs/mixins/index.js +79 -0
  56. package/dist/cjs/mixins/mixinHelpers.js +53 -0
  57. package/dist/cjs/models/interfaces.js +20 -0
  58. package/dist/cjs/models/session.js +2 -0
  59. package/dist/cjs/shared/index.js +70 -0
  60. package/dist/cjs/shared/utils/colorUtils.js +153 -0
  61. package/dist/cjs/shared/utils/debugUtils.js +73 -0
  62. package/dist/cjs/shared/utils/errorUtils.js +183 -0
  63. package/dist/cjs/shared/utils/index.js +49 -0
  64. package/dist/cjs/shared/utils/networkUtils.js +183 -0
  65. package/dist/cjs/shared/utils/themeUtils.js +106 -0
  66. package/dist/cjs/utils/apiUtils.js +61 -0
  67. package/dist/cjs/utils/asyncUtils.js +194 -0
  68. package/dist/cjs/utils/cache.js +226 -0
  69. package/dist/cjs/utils/deviceManager.js +205 -0
  70. package/dist/cjs/utils/errorUtils.js +154 -0
  71. package/dist/cjs/utils/index.js +26 -0
  72. package/dist/cjs/utils/languageUtils.js +165 -0
  73. package/dist/cjs/utils/loggerUtils.js +126 -0
  74. package/dist/cjs/utils/platform.js +144 -0
  75. package/dist/cjs/utils/requestUtils.js +209 -0
  76. package/dist/cjs/utils/sessionUtils.js +181 -0
  77. package/dist/cjs/utils/validationUtils.js +173 -0
  78. package/dist/esm/AuthManager.js +356 -0
  79. package/dist/esm/CrossDomainAuth.js +253 -0
  80. package/dist/esm/HttpService.js +614 -0
  81. package/dist/esm/OxyServices.base.js +259 -0
  82. package/dist/esm/OxyServices.errors.js +17 -0
  83. package/dist/esm/OxyServices.js +59 -0
  84. package/dist/esm/constants/version.js +13 -0
  85. package/dist/esm/crypto/index.js +13 -0
  86. package/dist/esm/crypto/keyManager.js +850 -0
  87. package/dist/esm/crypto/polyfill.js +61 -0
  88. package/dist/esm/crypto/recoveryPhrase.js +132 -0
  89. package/dist/esm/crypto/signatureService.js +259 -0
  90. package/dist/esm/i18n/index.js +69 -0
  91. package/dist/esm/i18n/locales/ar-SA.json +120 -0
  92. package/dist/esm/i18n/locales/ca-ES.json +120 -0
  93. package/dist/esm/i18n/locales/de-DE.json +120 -0
  94. package/dist/esm/i18n/locales/en-US.json +956 -0
  95. package/dist/esm/i18n/locales/es-ES.json +944 -0
  96. package/dist/esm/i18n/locales/fr-FR.json +120 -0
  97. package/dist/esm/i18n/locales/it-IT.json +120 -0
  98. package/dist/esm/i18n/locales/ja-JP.json +119 -0
  99. package/dist/esm/i18n/locales/ko-KR.json +120 -0
  100. package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
  101. package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
  102. package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
  103. package/dist/esm/i18n/locales/locales/en-US.json +956 -0
  104. package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
  105. package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
  106. package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
  107. package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
  108. package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
  109. package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
  110. package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
  111. package/dist/esm/i18n/locales/pt-PT.json +120 -0
  112. package/dist/esm/i18n/locales/zh-CN.json +120 -0
  113. package/dist/esm/index.js +55 -0
  114. package/dist/esm/mixins/OxyServices.analytics.js +46 -0
  115. package/dist/esm/mixins/OxyServices.assets.js +377 -0
  116. package/dist/esm/mixins/OxyServices.auth.js +256 -0
  117. package/dist/esm/mixins/OxyServices.developer.js +94 -0
  118. package/dist/esm/mixins/OxyServices.devices.js +113 -0
  119. package/dist/esm/mixins/OxyServices.features.js +306 -0
  120. package/dist/esm/mixins/OxyServices.fedcm.js +433 -0
  121. package/dist/esm/mixins/OxyServices.karma.js +105 -0
  122. package/dist/esm/mixins/OxyServices.language.js +118 -0
  123. package/dist/esm/mixins/OxyServices.location.js +40 -0
  124. package/dist/esm/mixins/OxyServices.payment.js +155 -0
  125. package/dist/esm/mixins/OxyServices.popup.js +369 -0
  126. package/dist/esm/mixins/OxyServices.privacy.js +159 -0
  127. package/dist/esm/mixins/OxyServices.redirect.js +343 -0
  128. package/dist/esm/mixins/OxyServices.security.js +78 -0
  129. package/dist/esm/mixins/OxyServices.user.js +352 -0
  130. package/dist/esm/mixins/OxyServices.utility.js +153 -0
  131. package/dist/esm/mixins/index.js +76 -0
  132. package/dist/esm/mixins/mixinHelpers.js +48 -0
  133. package/dist/esm/models/interfaces.js +17 -0
  134. package/dist/esm/models/session.js +1 -0
  135. package/dist/esm/shared/index.js +31 -0
  136. package/dist/esm/shared/utils/colorUtils.js +143 -0
  137. package/dist/esm/shared/utils/debugUtils.js +65 -0
  138. package/dist/esm/shared/utils/errorUtils.js +170 -0
  139. package/dist/esm/shared/utils/index.js +15 -0
  140. package/dist/esm/shared/utils/networkUtils.js +173 -0
  141. package/dist/esm/shared/utils/themeUtils.js +98 -0
  142. package/dist/esm/utils/apiUtils.js +55 -0
  143. package/dist/esm/utils/asyncUtils.js +179 -0
  144. package/dist/esm/utils/cache.js +218 -0
  145. package/dist/esm/utils/deviceManager.js +168 -0
  146. package/dist/esm/utils/errorUtils.js +146 -0
  147. package/dist/esm/utils/index.js +7 -0
  148. package/dist/esm/utils/languageUtils.js +158 -0
  149. package/dist/esm/utils/loggerUtils.js +115 -0
  150. package/dist/esm/utils/platform.js +102 -0
  151. package/dist/esm/utils/requestUtils.js +203 -0
  152. package/dist/esm/utils/sessionUtils.js +171 -0
  153. package/dist/esm/utils/validationUtils.js +153 -0
  154. package/dist/types/AuthManager.d.ts +143 -0
  155. package/dist/types/CrossDomainAuth.d.ts +160 -0
  156. package/dist/types/HttpService.d.ts +163 -0
  157. package/dist/types/OxyServices.base.d.ts +126 -0
  158. package/dist/types/OxyServices.d.ts +81 -0
  159. package/dist/types/OxyServices.errors.d.ts +11 -0
  160. package/dist/types/constants/version.d.ts +13 -0
  161. package/dist/types/crypto/index.d.ts +11 -0
  162. package/dist/types/crypto/keyManager.d.ts +189 -0
  163. package/dist/types/crypto/polyfill.d.ts +11 -0
  164. package/dist/types/crypto/recoveryPhrase.d.ts +58 -0
  165. package/dist/types/crypto/signatureService.d.ts +86 -0
  166. package/dist/types/i18n/index.d.ts +3 -0
  167. package/dist/types/index.d.ts +50 -0
  168. package/dist/types/mixins/OxyServices.analytics.d.ts +66 -0
  169. package/dist/types/mixins/OxyServices.assets.d.ts +135 -0
  170. package/dist/types/mixins/OxyServices.auth.d.ts +186 -0
  171. package/dist/types/mixins/OxyServices.developer.d.ts +99 -0
  172. package/dist/types/mixins/OxyServices.devices.d.ts +96 -0
  173. package/dist/types/mixins/OxyServices.features.d.ts +228 -0
  174. package/dist/types/mixins/OxyServices.fedcm.d.ts +200 -0
  175. package/dist/types/mixins/OxyServices.karma.d.ts +85 -0
  176. package/dist/types/mixins/OxyServices.language.d.ts +81 -0
  177. package/dist/types/mixins/OxyServices.location.d.ts +64 -0
  178. package/dist/types/mixins/OxyServices.payment.d.ts +111 -0
  179. package/dist/types/mixins/OxyServices.popup.d.ts +205 -0
  180. package/dist/types/mixins/OxyServices.privacy.d.ts +122 -0
  181. package/dist/types/mixins/OxyServices.redirect.d.ts +245 -0
  182. package/dist/types/mixins/OxyServices.security.d.ts +78 -0
  183. package/dist/types/mixins/OxyServices.user.d.ts +182 -0
  184. package/dist/types/mixins/OxyServices.utility.d.ts +93 -0
  185. package/dist/types/mixins/index.d.ts +30 -0
  186. package/dist/types/mixins/mixinHelpers.d.ts +31 -0
  187. package/dist/types/models/interfaces.d.ts +415 -0
  188. package/dist/types/models/session.d.ts +27 -0
  189. package/dist/types/shared/index.d.ts +28 -0
  190. package/dist/types/shared/utils/colorUtils.d.ts +104 -0
  191. package/dist/types/shared/utils/debugUtils.d.ts +48 -0
  192. package/dist/types/shared/utils/errorUtils.d.ts +97 -0
  193. package/dist/types/shared/utils/index.d.ts +13 -0
  194. package/dist/types/shared/utils/networkUtils.d.ts +139 -0
  195. package/dist/types/shared/utils/themeUtils.d.ts +90 -0
  196. package/dist/types/utils/apiUtils.d.ts +53 -0
  197. package/dist/types/utils/asyncUtils.d.ts +58 -0
  198. package/dist/types/utils/cache.d.ts +127 -0
  199. package/dist/types/utils/deviceManager.d.ts +65 -0
  200. package/dist/types/utils/errorUtils.d.ts +46 -0
  201. package/dist/types/utils/index.d.ts +6 -0
  202. package/dist/types/utils/languageUtils.d.ts +37 -0
  203. package/dist/types/utils/loggerUtils.d.ts +48 -0
  204. package/dist/types/utils/platform.d.ts +40 -0
  205. package/dist/types/utils/requestUtils.d.ts +123 -0
  206. package/dist/types/utils/sessionUtils.d.ts +54 -0
  207. package/dist/types/utils/validationUtils.d.ts +85 -0
  208. package/package.json +84 -0
  209. package/src/AuthManager.ts +436 -0
  210. package/src/CrossDomainAuth.ts +307 -0
  211. package/src/HttpService.ts +752 -0
  212. package/src/OxyServices.base.ts +334 -0
  213. package/src/OxyServices.errors.ts +26 -0
  214. package/src/OxyServices.ts +129 -0
  215. package/src/constants/version.ts +15 -0
  216. package/src/crypto/index.ts +25 -0
  217. package/src/crypto/keyManager.ts +962 -0
  218. package/src/crypto/polyfill.ts +70 -0
  219. package/src/crypto/recoveryPhrase.ts +166 -0
  220. package/src/crypto/signatureService.ts +323 -0
  221. package/src/i18n/index.ts +75 -0
  222. package/src/i18n/locales/ar-SA.json +120 -0
  223. package/src/i18n/locales/ca-ES.json +120 -0
  224. package/src/i18n/locales/de-DE.json +120 -0
  225. package/src/i18n/locales/en-US.json +956 -0
  226. package/src/i18n/locales/es-ES.json +944 -0
  227. package/src/i18n/locales/fr-FR.json +120 -0
  228. package/src/i18n/locales/it-IT.json +120 -0
  229. package/src/i18n/locales/ja-JP.json +119 -0
  230. package/src/i18n/locales/ko-KR.json +120 -0
  231. package/src/i18n/locales/pt-PT.json +120 -0
  232. package/src/i18n/locales/zh-CN.json +120 -0
  233. package/src/index.ts +153 -0
  234. package/src/mixins/OxyServices.analytics.ts +53 -0
  235. package/src/mixins/OxyServices.assets.ts +412 -0
  236. package/src/mixins/OxyServices.auth.ts +358 -0
  237. package/src/mixins/OxyServices.developer.ts +114 -0
  238. package/src/mixins/OxyServices.devices.ts +119 -0
  239. package/src/mixins/OxyServices.features.ts +428 -0
  240. package/src/mixins/OxyServices.fedcm.ts +494 -0
  241. package/src/mixins/OxyServices.karma.ts +111 -0
  242. package/src/mixins/OxyServices.language.ts +127 -0
  243. package/src/mixins/OxyServices.location.ts +46 -0
  244. package/src/mixins/OxyServices.payment.ts +163 -0
  245. package/src/mixins/OxyServices.popup.ts +443 -0
  246. package/src/mixins/OxyServices.privacy.ts +182 -0
  247. package/src/mixins/OxyServices.redirect.ts +397 -0
  248. package/src/mixins/OxyServices.security.ts +103 -0
  249. package/src/mixins/OxyServices.user.ts +392 -0
  250. package/src/mixins/OxyServices.utility.ts +191 -0
  251. package/src/mixins/index.ts +91 -0
  252. package/src/mixins/mixinHelpers.ts +69 -0
  253. package/src/models/interfaces.ts +511 -0
  254. package/src/models/session.ts +30 -0
  255. package/src/shared/index.ts +82 -0
  256. package/src/shared/utils/colorUtils.ts +155 -0
  257. package/src/shared/utils/debugUtils.ts +73 -0
  258. package/src/shared/utils/errorUtils.ts +181 -0
  259. package/src/shared/utils/index.ts +59 -0
  260. package/src/shared/utils/networkUtils.ts +248 -0
  261. package/src/shared/utils/themeUtils.ts +115 -0
  262. package/src/types/bip39.d.ts +32 -0
  263. package/src/types/buffer.d.ts +97 -0
  264. package/src/types/color.d.ts +20 -0
  265. package/src/types/elliptic.d.ts +62 -0
  266. package/src/utils/apiUtils.ts +88 -0
  267. package/src/utils/asyncUtils.ts +252 -0
  268. package/src/utils/cache.ts +264 -0
  269. package/src/utils/deviceManager.ts +198 -0
  270. package/src/utils/errorUtils.ts +216 -0
  271. package/src/utils/index.ts +21 -0
  272. package/src/utils/languageUtils.ts +174 -0
  273. package/src/utils/loggerUtils.ts +153 -0
  274. package/src/utils/platform.ts +117 -0
  275. package/src/utils/requestUtils.ts +237 -0
  276. package/src/utils/sessionUtils.ts +206 -0
  277. package/src/utils/validationUtils.ts +174 -0
@@ -0,0 +1,433 @@
1
+ import { OxyAuthenticationError } from '../OxyServices.errors';
2
+ import { createDebugLogger } from '../shared/utils/debugUtils';
3
+ const debug = createDebugLogger('FedCM');
4
+ // Global lock to prevent concurrent FedCM requests
5
+ // FedCM only allows one navigator.credentials.get request at a time
6
+ let fedCMRequestInProgress = false;
7
+ let fedCMRequestPromise = null;
8
+ let currentMediationMode = null;
9
+ /**
10
+ * Federated Credential Management (FedCM) Authentication Mixin
11
+ *
12
+ * Implements the modern browser-native identity federation API that enables
13
+ * Google-style cross-domain authentication without third-party cookies.
14
+ *
15
+ * Browser Support:
16
+ * - Chrome 108+
17
+ * - Safari 16.4+
18
+ * - Edge 108+
19
+ * - Firefox: Not yet supported (fallback required)
20
+ *
21
+ * Key Features:
22
+ * - No redirects or popups required
23
+ * - Browser-native UI prompts
24
+ * - Privacy-preserving (IdP can't track users)
25
+ * - Automatic SSO across domains
26
+ * - Silent re-authentication support
27
+ *
28
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/FedCM_API
29
+ */
30
+ export function OxyServicesFedCMMixin(Base) {
31
+ var _a;
32
+ return _a = class extends Base {
33
+ constructor(...args) {
34
+ super(...args);
35
+ }
36
+ /**
37
+ * Check if FedCM is supported in the current browser
38
+ */
39
+ static isFedCMSupported() {
40
+ if (typeof window === 'undefined')
41
+ return false;
42
+ return 'IdentityCredential' in window && 'navigator' in window && 'credentials' in navigator;
43
+ }
44
+ /**
45
+ * Instance method to check FedCM support
46
+ */
47
+ isFedCMSupported() {
48
+ return this.constructor.isFedCMSupported();
49
+ }
50
+ /**
51
+ * Sign in using FedCM (Federated Credential Management API)
52
+ *
53
+ * This provides a Google-style authentication experience:
54
+ * - Browser shows native "Sign in with Oxy" prompt
55
+ * - No redirect or popup required
56
+ * - User approves → credential exchange happens in browser
57
+ * - All apps automatically get SSO after first sign-in
58
+ *
59
+ * @param options - Authentication options
60
+ * @returns Session with access token and user data
61
+ * @throws {OxyAuthenticationError} If FedCM not supported or user cancels
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * try {
66
+ * const session = await oxyServices.signInWithFedCM();
67
+ * console.log('Signed in:', session.user);
68
+ * } catch (error) {
69
+ * // Fallback to popup or redirect auth
70
+ * await oxyServices.signInWithPopup();
71
+ * }
72
+ * ```
73
+ */
74
+ async signInWithFedCM(options = {}) {
75
+ if (!this.isFedCMSupported()) {
76
+ throw new OxyAuthenticationError('FedCM not supported in this browser. Please update your browser or use an alternative sign-in method.');
77
+ }
78
+ try {
79
+ const nonce = options.nonce || this.generateNonce();
80
+ const clientId = this.getClientId();
81
+ debug.log('Interactive sign-in: Requesting credential for', clientId);
82
+ // Request credential from browser's native identity flow
83
+ const credential = await this.requestIdentityCredential({
84
+ configURL: this.constructor.DEFAULT_CONFIG_URL,
85
+ clientId,
86
+ nonce,
87
+ context: options.context,
88
+ });
89
+ if (!credential || !credential.token) {
90
+ throw new OxyAuthenticationError('No credential received from browser');
91
+ }
92
+ debug.log('Interactive sign-in: Got credential, exchanging for session');
93
+ // Exchange FedCM ID token for Oxy session
94
+ const session = await this.exchangeIdTokenForSession(credential.token);
95
+ // Store access token in HttpService (extract from response or get from session)
96
+ if (session && session.accessToken) {
97
+ this.httpService.setTokens(session.accessToken);
98
+ }
99
+ debug.log('Interactive sign-in: Success!', { userId: session?.user?.id });
100
+ return session;
101
+ }
102
+ catch (error) {
103
+ debug.log('Interactive sign-in failed:', error);
104
+ if (error.name === 'AbortError') {
105
+ throw new OxyAuthenticationError('Sign-in was cancelled by user');
106
+ }
107
+ if (error.name === 'NetworkError') {
108
+ throw new OxyAuthenticationError('Network error during sign-in. Please check your connection.');
109
+ }
110
+ throw error;
111
+ }
112
+ }
113
+ /**
114
+ * Silent sign-in using FedCM
115
+ *
116
+ * Attempts to automatically re-authenticate the user without any UI.
117
+ * This is what enables "instant sign-in" across all Oxy domains after
118
+ * the user has signed in once.
119
+ *
120
+ * The browser will:
121
+ * 1. Check if user has previously signed in to Oxy
122
+ * 2. Check if user is still signed in at auth.oxy.so
123
+ * 3. If yes, automatically provide credential without prompting
124
+ *
125
+ * @returns Session if user is already signed in, null otherwise
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * // On app startup
130
+ * useEffect(() => {
131
+ * const checkAuth = async () => {
132
+ * const session = await oxyServices.silentSignInWithFedCM();
133
+ * if (session) {
134
+ * setUser(session.user);
135
+ * } else {
136
+ * // Show sign-in button
137
+ * }
138
+ * };
139
+ * checkAuth();
140
+ * }, []);
141
+ * ```
142
+ */
143
+ async silentSignInWithFedCM() {
144
+ if (!this.isFedCMSupported()) {
145
+ debug.log('Silent SSO: FedCM not supported in this browser');
146
+ return null;
147
+ }
148
+ const clientId = this.getClientId();
149
+ debug.log('Silent SSO: Starting for', clientId);
150
+ // First try silent mediation (no UI) - works if user previously consented
151
+ let credential = null;
152
+ try {
153
+ const nonce = this.generateNonce();
154
+ debug.log('Silent SSO: Attempting silent mediation...');
155
+ credential = await this.requestIdentityCredential({
156
+ configURL: this.constructor.DEFAULT_CONFIG_URL,
157
+ clientId,
158
+ nonce,
159
+ mediation: 'silent',
160
+ });
161
+ debug.log('Silent SSO: Silent mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
162
+ }
163
+ catch (silentError) {
164
+ // Silent mediation failed - this is expected if user hasn't consented before or is in quiet period
165
+ const errorName = silentError instanceof Error ? silentError.name : 'Unknown';
166
+ 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
+ }
188
+ }
189
+ if (!credential || !credential.token) {
190
+ debug.log('Silent SSO: No credential returned (user may have dismissed prompt or is not logged in at IdP)');
191
+ return null;
192
+ }
193
+ debug.log('Silent SSO: Got credential, exchanging for session...');
194
+ let session;
195
+ try {
196
+ session = await this.exchangeIdTokenForSession(credential.token);
197
+ }
198
+ catch (exchangeError) {
199
+ debug.error('Silent SSO: Token exchange failed:', exchangeError);
200
+ return null;
201
+ }
202
+ // Validate session response has required fields
203
+ if (!session) {
204
+ debug.error('Silent SSO: Exchange returned null session');
205
+ return null;
206
+ }
207
+ if (!session.sessionId) {
208
+ debug.error('Silent SSO: Exchange returned session without sessionId:', session);
209
+ return null;
210
+ }
211
+ if (!session.user) {
212
+ debug.error('Silent SSO: Exchange returned session without user:', session);
213
+ return null;
214
+ }
215
+ // Set the access token
216
+ if (session.accessToken) {
217
+ this.httpService.setTokens(session.accessToken);
218
+ debug.log('Silent SSO: Access token set');
219
+ }
220
+ else {
221
+ debug.warn('Silent SSO: No accessToken in session response');
222
+ }
223
+ debug.log('Silent SSO: Success!', {
224
+ sessionId: session.sessionId?.substring(0, 8) + '...',
225
+ userId: session.user?.id
226
+ });
227
+ return session;
228
+ }
229
+ /**
230
+ * Request identity credential from browser using FedCM API
231
+ *
232
+ * Uses a global lock to prevent concurrent requests, as FedCM only
233
+ * allows one navigator.credentials.get request at a time.
234
+ *
235
+ * Interactive requests (optional/required) wait for any silent request to finish first.
236
+ *
237
+ * @private
238
+ */
239
+ async requestIdentityCredential(options) {
240
+ const requestedMediation = options.mediation || 'optional';
241
+ const isInteractive = requestedMediation !== 'silent';
242
+ debug.log('requestIdentityCredential called:', {
243
+ mediation: requestedMediation,
244
+ clientId: options.clientId,
245
+ inProgress: fedCMRequestInProgress,
246
+ });
247
+ // If a request is already in progress...
248
+ if (fedCMRequestInProgress && fedCMRequestPromise) {
249
+ debug.log('Request already in progress, waiting...');
250
+ // If current request is silent and new request is interactive,
251
+ // wait for silent to finish, then make the interactive request
252
+ if (currentMediationMode === 'silent' && isInteractive) {
253
+ try {
254
+ await fedCMRequestPromise;
255
+ }
256
+ catch {
257
+ // Ignore silent request errors
258
+ }
259
+ // Now fall through to make the interactive request
260
+ }
261
+ else {
262
+ // Same type of request - wait for the existing one
263
+ try {
264
+ return await fedCMRequestPromise;
265
+ }
266
+ catch {
267
+ return null;
268
+ }
269
+ }
270
+ }
271
+ fedCMRequestInProgress = true;
272
+ currentMediationMode = requestedMediation;
273
+ const controller = new AbortController();
274
+ // Use shorter timeout for silent mediation since it should be quick
275
+ const timeoutMs = requestedMediation === 'silent'
276
+ ? this.constructor.FEDCM_SILENT_TIMEOUT
277
+ : this.constructor.FEDCM_TIMEOUT;
278
+ const timeout = setTimeout(() => {
279
+ debug.log('Request timed out after', timeoutMs, 'ms (mediation:', requestedMediation + ')');
280
+ controller.abort();
281
+ }, timeoutMs);
282
+ fedCMRequestPromise = (async () => {
283
+ try {
284
+ debug.log('Calling navigator.credentials.get with mediation:', requestedMediation);
285
+ // Type assertion needed as FedCM types may not be in all TypeScript versions
286
+ const credential = (await navigator.credentials.get({
287
+ identity: {
288
+ providers: [
289
+ {
290
+ configURL: options.configURL,
291
+ clientId: options.clientId,
292
+ // Send nonce at both levels for backward compatibility
293
+ nonce: options.nonce, // For older browsers
294
+ params: {
295
+ nonce: options.nonce, // For Chrome 145+
296
+ },
297
+ ...(options.context && { loginHint: options.context }),
298
+ },
299
+ ],
300
+ },
301
+ mediation: requestedMediation,
302
+ signal: controller.signal,
303
+ }));
304
+ debug.log('navigator.credentials.get returned:', {
305
+ hasCredential: !!credential,
306
+ type: credential?.type,
307
+ hasToken: !!credential?.token,
308
+ });
309
+ if (!credential || credential.type !== 'identity') {
310
+ debug.log('No valid identity credential returned');
311
+ return null;
312
+ }
313
+ debug.log('Got valid identity credential with token');
314
+ return { token: credential.token };
315
+ }
316
+ catch (error) {
317
+ const errorName = error instanceof Error ? error.name : 'Unknown';
318
+ const errorMessage = error instanceof Error ? error.message : String(error);
319
+ debug.log('navigator.credentials.get error:', { name: errorName, message: errorMessage });
320
+ throw error;
321
+ }
322
+ finally {
323
+ clearTimeout(timeout);
324
+ fedCMRequestInProgress = false;
325
+ fedCMRequestPromise = null;
326
+ currentMediationMode = null;
327
+ }
328
+ })();
329
+ return fedCMRequestPromise;
330
+ }
331
+ /**
332
+ * Exchange FedCM ID token for Oxy session
333
+ *
334
+ * The ID token is a JWT issued by auth.oxy.so that proves the user's
335
+ * identity. We exchange it for a full Oxy session with access token.
336
+ *
337
+ * @private
338
+ */
339
+ 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) + '...');
343
+ try {
344
+ 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,
348
+ hasUser: !!response?.user,
349
+ hasAccessToken: !!response?.accessToken,
350
+ userId: response?.user?.id,
351
+ username: response?.user?.username,
352
+ responseKeys: response ? Object.keys(response) : [],
353
+ });
354
+ return response;
355
+ }
356
+ 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
+ });
362
+ throw error;
363
+ }
364
+ }
365
+ /**
366
+ * Revoke FedCM credential (sign out)
367
+ *
368
+ * This tells the browser to forget the FedCM credential for this app.
369
+ * The user will need to re-authenticate next time.
370
+ */
371
+ async revokeFedCMCredential() {
372
+ if (!this.isFedCMSupported()) {
373
+ return;
374
+ }
375
+ try {
376
+ // FedCM logout API (if available)
377
+ if ('IdentityCredential' in window && 'logout' in window.IdentityCredential) {
378
+ const clientId = this.getClientId();
379
+ await window.IdentityCredential.logout({
380
+ configURL: this.constructor.DEFAULT_CONFIG_URL,
381
+ clientId,
382
+ });
383
+ }
384
+ }
385
+ catch (error) {
386
+ // Silent failure
387
+ }
388
+ }
389
+ /**
390
+ * Get configuration for FedCM
391
+ *
392
+ * @returns FedCM configuration with browser support info
393
+ */
394
+ getFedCMConfig() {
395
+ return {
396
+ enabled: this.isFedCMSupported(),
397
+ configURL: this.constructor.DEFAULT_CONFIG_URL,
398
+ clientId: this.getClientId(),
399
+ };
400
+ }
401
+ /**
402
+ * Generate a cryptographically secure nonce for FedCM
403
+ *
404
+ * @private
405
+ */
406
+ generateNonce() {
407
+ if (typeof window !== 'undefined' && window.crypto && window.crypto.randomUUID) {
408
+ return window.crypto.randomUUID();
409
+ }
410
+ // Fallback for older browsers
411
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
412
+ }
413
+ /**
414
+ * Get the client ID for this origin
415
+ *
416
+ * @private
417
+ */
418
+ getClientId() {
419
+ if (typeof window === 'undefined') {
420
+ return 'unknown';
421
+ }
422
+ return window.location.origin;
423
+ }
424
+ },
425
+ _a.DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json',
426
+ _a.FEDCM_TIMEOUT = 60000 // 1 minute for interactive
427
+ ,
428
+ _a.FEDCM_SILENT_TIMEOUT = 10000 // 10 seconds for silent mediation
429
+ ,
430
+ _a;
431
+ }
432
+ // Export the mixin function as both named and default
433
+ export { OxyServicesFedCMMixin as FedCMMixin };
@@ -0,0 +1,105 @@
1
+ import { CACHE_TIMES } from './mixinHelpers';
2
+ export function OxyServicesKarmaMixin(Base) {
3
+ return class extends Base {
4
+ constructor(...args) {
5
+ super(...args);
6
+ }
7
+ /**
8
+ * Get user karma
9
+ */
10
+ async getUserKarma(userId) {
11
+ try {
12
+ return await this.makeRequest('GET', `/api/karma/${userId}`, undefined, {
13
+ cache: true,
14
+ cacheTTL: 2 * 60 * 1000, // 2 minutes cache
15
+ });
16
+ }
17
+ catch (error) {
18
+ throw this.handleError(error);
19
+ }
20
+ }
21
+ /**
22
+ * Give karma to user
23
+ */
24
+ async giveKarma(userId, amount, reason) {
25
+ try {
26
+ return await this.makeRequest('POST', `/api/karma/${userId}/give`, {
27
+ amount,
28
+ reason
29
+ }, { cache: false });
30
+ }
31
+ catch (error) {
32
+ throw this.handleError(error);
33
+ }
34
+ }
35
+ /**
36
+ * Get user karma total
37
+ * @param userId - The user ID
38
+ * @returns User karma total
39
+ */
40
+ async getUserKarmaTotal(userId) {
41
+ try {
42
+ return await this.makeRequest('GET', `/api/karma/${userId}/total`, undefined, {
43
+ cache: true,
44
+ cacheTTL: CACHE_TIMES.MEDIUM,
45
+ });
46
+ }
47
+ catch (error) {
48
+ throw this.handleError(error);
49
+ }
50
+ }
51
+ /**
52
+ * Get user karma history
53
+ * @param userId - The user ID
54
+ * @param limit - Optional limit for results
55
+ * @param offset - Optional offset for pagination
56
+ * @returns User karma history
57
+ */
58
+ async getUserKarmaHistory(userId, limit, offset) {
59
+ try {
60
+ const params = {};
61
+ if (limit)
62
+ params.limit = limit;
63
+ if (offset)
64
+ params.offset = offset;
65
+ return await this.makeRequest('GET', `/api/karma/${userId}/history`, params, {
66
+ cache: true,
67
+ cacheTTL: CACHE_TIMES.MEDIUM,
68
+ });
69
+ }
70
+ catch (error) {
71
+ throw this.handleError(error);
72
+ }
73
+ }
74
+ /**
75
+ * Get karma leaderboard
76
+ * @returns Karma leaderboard
77
+ */
78
+ async getKarmaLeaderboard() {
79
+ try {
80
+ return await this.makeRequest('GET', '/api/karma/leaderboard', undefined, {
81
+ cache: true,
82
+ cacheTTL: CACHE_TIMES.LONG,
83
+ });
84
+ }
85
+ catch (error) {
86
+ throw this.handleError(error);
87
+ }
88
+ }
89
+ /**
90
+ * Get karma rules
91
+ * @returns Karma rules
92
+ */
93
+ async getKarmaRules() {
94
+ try {
95
+ return await this.makeRequest('GET', '/api/karma/rules', undefined, {
96
+ cache: true,
97
+ cacheTTL: CACHE_TIMES.EXTRA_LONG, // Rules don't change often
98
+ });
99
+ }
100
+ catch (error) {
101
+ throw this.handleError(error);
102
+ }
103
+ }
104
+ };
105
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Language Methods Mixin
3
+ */
4
+ import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils';
5
+ export function OxyServicesLanguageMixin(Base) {
6
+ return class extends Base {
7
+ constructor(...args) {
8
+ super(...args);
9
+ }
10
+ /**
11
+ * Get appropriate storage for the platform (similar to DeviceManager)
12
+ */
13
+ async getStorage() {
14
+ const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
15
+ if (isReactNative) {
16
+ try {
17
+ const asyncStorageModule = await import('@react-native-async-storage/async-storage');
18
+ const storage = asyncStorageModule.default;
19
+ return {
20
+ getItem: storage.getItem.bind(storage),
21
+ setItem: storage.setItem.bind(storage),
22
+ removeItem: storage.removeItem.bind(storage),
23
+ };
24
+ }
25
+ catch (error) {
26
+ console.error('AsyncStorage not available in React Native:', error);
27
+ throw new Error('AsyncStorage is required in React Native environment');
28
+ }
29
+ }
30
+ else {
31
+ // Use localStorage for web
32
+ return {
33
+ getItem: async (key) => {
34
+ if (typeof window !== 'undefined' && window.localStorage) {
35
+ return localStorage.getItem(key);
36
+ }
37
+ return null;
38
+ },
39
+ setItem: async (key, value) => {
40
+ if (typeof window !== 'undefined' && window.localStorage) {
41
+ localStorage.setItem(key, value);
42
+ }
43
+ },
44
+ removeItem: async (key) => {
45
+ if (typeof window !== 'undefined' && window.localStorage) {
46
+ localStorage.removeItem(key);
47
+ }
48
+ }
49
+ };
50
+ }
51
+ }
52
+ /**
53
+ * Get the current language from storage or user profile
54
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
55
+ * @returns The current language code (e.g., 'en-US') or null if not set
56
+ */
57
+ async getCurrentLanguage(storageKeyPrefix = 'oxy_session') {
58
+ try {
59
+ // First try to get from user profile if authenticated
60
+ try {
61
+ const user = await this.getCurrentUser();
62
+ const userLanguage = user?.language;
63
+ if (userLanguage) {
64
+ return normalizeLanguageCode(userLanguage) || userLanguage;
65
+ }
66
+ }
67
+ catch (e) {
68
+ // User not authenticated or error, continue to storage
69
+ }
70
+ // Fall back to storage
71
+ const storage = await this.getStorage();
72
+ const storageKey = `${storageKeyPrefix}_language`;
73
+ const storedLanguage = await storage.getItem(storageKey);
74
+ if (storedLanguage) {
75
+ return normalizeLanguageCode(storedLanguage) || storedLanguage;
76
+ }
77
+ return null;
78
+ }
79
+ catch (error) {
80
+ if (__DEV__) {
81
+ console.warn('Failed to get current language:', error);
82
+ }
83
+ return null;
84
+ }
85
+ }
86
+ /**
87
+ * Get the current language with metadata (name, nativeName, etc.)
88
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
89
+ * @returns Language metadata object or null if not set
90
+ */
91
+ async getCurrentLanguageMetadata(storageKeyPrefix = 'oxy_session') {
92
+ const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
93
+ return getLanguageMetadata(languageCode);
94
+ }
95
+ /**
96
+ * Get the current language name (e.g., 'English')
97
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
98
+ * @returns Language name or null if not set
99
+ */
100
+ async getCurrentLanguageName(storageKeyPrefix = 'oxy_session') {
101
+ const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
102
+ if (!languageCode)
103
+ return null;
104
+ return getLanguageName(languageCode);
105
+ }
106
+ /**
107
+ * Get the current native language name (e.g., 'Español')
108
+ * @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
109
+ * @returns Native language name or null if not set
110
+ */
111
+ async getCurrentNativeLanguageName(storageKeyPrefix = 'oxy_session') {
112
+ const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
113
+ if (!languageCode)
114
+ return null;
115
+ return getNativeLanguageName(languageCode);
116
+ }
117
+ };
118
+ }