@oxyhq/services 5.19.0 → 5.20.1

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 (43) hide show
  1. package/README.md +51 -42
  2. package/lib/commonjs/core/mixins/OxyServices.fedcm.js +46 -24
  3. package/lib/commonjs/core/mixins/OxyServices.fedcm.js.map +1 -1
  4. package/lib/commonjs/ui/components/OxyProvider.js +106 -40
  5. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  6. package/lib/commonjs/ui/components/WebOxyProvider.js +9 -10
  7. package/lib/commonjs/ui/components/WebOxyProvider.js.map +1 -1
  8. package/lib/commonjs/ui/hooks/useAuth.js +21 -8
  9. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  10. package/lib/commonjs/ui/hooks/useWebSSO.js +21 -2
  11. package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
  12. package/lib/module/core/mixins/OxyServices.fedcm.js +46 -24
  13. package/lib/module/core/mixins/OxyServices.fedcm.js.map +1 -1
  14. package/lib/module/ui/components/OxyProvider.js +106 -39
  15. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  16. package/lib/module/ui/components/WebOxyProvider.js +9 -10
  17. package/lib/module/ui/components/WebOxyProvider.js.map +1 -1
  18. package/lib/module/ui/hooks/useAuth.js +21 -8
  19. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  20. package/lib/module/ui/hooks/useWebSSO.js +21 -2
  21. package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
  22. package/lib/typescript/commonjs/core/mixins/OxyServices.fedcm.d.ts +10 -1
  23. package/lib/typescript/commonjs/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +26 -3
  25. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts +9 -10
  27. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
  30. package/lib/typescript/module/core/mixins/OxyServices.fedcm.d.ts +10 -1
  31. package/lib/typescript/module/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
  32. package/lib/typescript/module/ui/components/OxyProvider.d.ts +26 -3
  33. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  34. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts +9 -10
  35. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts.map +1 -1
  36. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  37. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/core/mixins/OxyServices.fedcm.ts +47 -23
  40. package/src/ui/components/OxyProvider.tsx +112 -47
  41. package/src/ui/components/WebOxyProvider.tsx +9 -10
  42. package/src/ui/hooks/useAuth.ts +25 -8
  43. package/src/ui/hooks/useWebSSO.ts +21 -2
@@ -1,10 +1,33 @@
1
1
  import { type FC } from 'react';
2
2
  import type { OxyProviderProps } from '../types/navigation';
3
3
  /**
4
- * OxyProvider component
4
+ * OxyProvider - Universal provider for Expo apps (native + web)
5
5
  *
6
- * Provides the authentication/session context used across the app.
7
- * UI composition (e.g. OxyRouter inside a bottom sheet) can be added externally.
6
+ * Zero-config authentication and session management:
7
+ * - Native (iOS/Android): Keychain-based identity, bottom sheet auth UI
8
+ * - Web: FedCM cross-domain SSO, popup fallback
9
+ *
10
+ * Usage:
11
+ * ```tsx
12
+ * import { OxyProvider, useAuth } from '@oxyhq/services';
13
+ *
14
+ * function App() {
15
+ * return (
16
+ * <OxyProvider baseURL="https://api.oxy.so">
17
+ * <YourApp />
18
+ * </OxyProvider>
19
+ * );
20
+ * }
21
+ *
22
+ * function MyComponent() {
23
+ * const { isAuthenticated, user, signIn, signOut } = useAuth();
24
+ *
25
+ * if (!isAuthenticated) {
26
+ * return <OxySignInButton />;
27
+ * }
28
+ * return <Text>Welcome, {user?.username}!</Text>;
29
+ * }
30
+ * ```
8
31
  */
9
32
  declare const OxyProvider: FC<OxyProviderProps>;
10
33
  export default OxyProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"OxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/OxyProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAK7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAY5D;;;;;GAKG;AACH,QAAA,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA4IrC,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"OxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/OxyProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAI7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA4B5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAuKrC,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,16 +1,18 @@
1
1
  /**
2
- * WebOxyProvider - OxyProvider for web apps (Next.js, React)
2
+ * WebOxyProvider - Lightweight provider for pure React/Next.js apps
3
3
  *
4
- * This provider is specifically for web environments and doesn't include
5
- * React Native-specific dependencies. It provides:
4
+ * Use this provider for web apps that DON'T use Expo/React Native.
5
+ * For Expo apps (native + web), use `OxyProvider` instead - it works on all platforms.
6
+ *
7
+ * Features:
6
8
  * - Automatic cross-domain SSO via FedCM (Chrome 108+, Safari 16.4+, Edge 108+)
9
+ * - No React Native dependencies
7
10
  * - Session management
8
11
  * - All useOxy/useAuth functionality
9
12
  *
10
- * Zero-config: Just wrap your app and SSO works automatically across domains.
11
- *
12
13
  * Usage:
13
14
  * ```tsx
15
+ * // For pure React/Next.js apps (no Expo):
14
16
  * import { WebOxyProvider, useAuth } from '@oxyhq/services';
15
17
  *
16
18
  * function App() {
@@ -21,11 +23,8 @@
21
23
  * );
22
24
  * }
23
25
  *
24
- * function LoginButton() {
25
- * const { isAuthenticated, signIn, user } = useAuth();
26
- * if (isAuthenticated) return <span>Welcome, {user?.username}!</span>;
27
- * return <button onClick={() => signIn()}>Sign In</button>;
28
- * }
26
+ * // For Expo apps (native + web), use OxyProvider instead:
27
+ * import { OxyProvider, useAuth } from '@oxyhq/services';
29
28
  * ```
30
29
  */
31
30
  import { type FC, type ReactNode } from 'react';
@@ -1 +1 @@
1
- {"version":3,"file":"WebOxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/WebOxyProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAA+B,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,QAAA,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA2D3C,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"WebOxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/WebOxyProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAA+B,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,QAAA,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA2D3C,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;CACvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,IAAI,aAAa,CAoGvC;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;CACvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,IAAI,aAAa,CAqHvC;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAI/B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CAiEpC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAI/B;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CA0EpC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -95,6 +95,9 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
95
95
  /**
96
96
  * Request identity credential from browser using FedCM API
97
97
  *
98
+ * Uses a global lock to prevent concurrent requests, as FedCM only
99
+ * allows one navigator.credentials.get request at a time.
100
+ *
98
101
  * @private
99
102
  */
100
103
  requestIdentityCredential(options: {
@@ -178,7 +181,13 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
178
181
  handleError(error: unknown): Error;
179
182
  healthCheck(): Promise<{
180
183
  status: string;
181
- users?: number;
184
+ users
185
+ /**
186
+ * Get configuration for FedCM
187
+ *
188
+ * @returns FedCM configuration with browser support info
189
+ */
190
+ ?: number;
182
191
  timestamp?: string;
183
192
  [key: string]: any;
184
193
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"OxyServices.fedcm.d.ts","sourceRoot":"","sources":["../../../../../src/core/mixins/OxyServices.fedcm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAC;CACpD;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,OAAO,eAAe,EAAE,IAAI,EAAE,CAAC;kBAEtD,GAAG,EAAE;QAc5B;;WAEG;4BACiB,OAAO;QAI3B;;;;;;;;;;;;;;;;;;;;;;;WAuBG;kCAC4B,gBAAgB,GAAQ,OAAO,CAAC,oBAAoB,CAAC;QA2CpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA6BG;iCAC4B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;QAiCnE;;;;WAIG;2CAC6C;YAC9C,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,EAAE,MAAM,CAAC;YACd,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,SAAS,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;SAChD,GAAG,OAAO,CAAC;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QA+BrC;;;;;;;WAOG;2CAC6C,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;QAStF;;;;;WAKG;iCAC4B,OAAO,CAAC,IAAI,CAAC;QAmB5C;;;;WAIG;0BACe,WAAW;QAQ7B;;;;WAIG;yBACqB,MAAM;QAQ9B;;;;WAIG;uBACmB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAhH3B,CAAF;sBAEM,CAAC;yBAA6B,CAAC;;;;;;iBAgHzB,CAAA;qBAEN,CAAC;;;;iCA3QsC,gCAAgC;4BACrC,KAAK;IAE5C;;OAEG;wBACwB,OAAO;;MAwQnC;AAGD,OAAO,EAAE,qBAAqB,IAAI,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"OxyServices.fedcm.d.ts","sourceRoot":"","sources":["../../../../../src/core/mixins/OxyServices.fedcm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAC;CACpD;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,OAAO,eAAe,EAAE,IAAI,EAAE,CAAC;kBAEtD,GAAG,EAAE;QAc5B;;WAEG;4BACiB,OAAO;QAI3B;;;;;;;;;;;;;;;;;;;;;;;WAuBG;kCAC4B,gBAAgB,GAAQ,OAAO,CAAC,oBAAoB,CAAC;QA2CpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA6BG;iCAC4B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;QAiCnE;;;;;;;WAOG;2CAC6C;YAC9C,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,EAAE,MAAM,CAAC;YACd,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,SAAS,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;SAChD,GAAG,OAAO,CAAC;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QA+CrC;;;;;;;WAOG;2CAC6C,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;QAStF;;;;;WAKG;iCAC4B,OAAO,CAAC,IAAI,CAAC;QAmB5C;;;;WAIG;0BACe,WAAW;QAQ7B;;;;WAIG;yBACqB,MAAM;QAQ9B;;;;WAIG;uBACmB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA/IxB,CAAD;sBAGD,CAAH;yBACQ,CAAA;;;;;;;YA4GP;;;;eAIG;YACH,CANF;qBAEiB,CAAC;;;;iCA5P4B,gCAAgC;4BACrC,KAAK;IAE5C;;OAEG;wBACwB,OAAO;;MA2RnC;AAGD,OAAO,EAAE,qBAAqB,IAAI,UAAU,EAAE,CAAC"}
@@ -1,10 +1,33 @@
1
1
  import { type FC } from 'react';
2
2
  import type { OxyProviderProps } from '../types/navigation';
3
3
  /**
4
- * OxyProvider component
4
+ * OxyProvider - Universal provider for Expo apps (native + web)
5
5
  *
6
- * Provides the authentication/session context used across the app.
7
- * UI composition (e.g. OxyRouter inside a bottom sheet) can be added externally.
6
+ * Zero-config authentication and session management:
7
+ * - Native (iOS/Android): Keychain-based identity, bottom sheet auth UI
8
+ * - Web: FedCM cross-domain SSO, popup fallback
9
+ *
10
+ * Usage:
11
+ * ```tsx
12
+ * import { OxyProvider, useAuth } from '@oxyhq/services';
13
+ *
14
+ * function App() {
15
+ * return (
16
+ * <OxyProvider baseURL="https://api.oxy.so">
17
+ * <YourApp />
18
+ * </OxyProvider>
19
+ * );
20
+ * }
21
+ *
22
+ * function MyComponent() {
23
+ * const { isAuthenticated, user, signIn, signOut } = useAuth();
24
+ *
25
+ * if (!isAuthenticated) {
26
+ * return <OxySignInButton />;
27
+ * }
28
+ * return <Text>Welcome, {user?.username}!</Text>;
29
+ * }
30
+ * ```
8
31
  */
9
32
  declare const OxyProvider: FC<OxyProviderProps>;
10
33
  export default OxyProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"OxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/OxyProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAK7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAY5D;;;;;GAKG;AACH,QAAA,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA4IrC,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"OxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/OxyProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAI7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA4B5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAuKrC,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,16 +1,18 @@
1
1
  /**
2
- * WebOxyProvider - OxyProvider for web apps (Next.js, React)
2
+ * WebOxyProvider - Lightweight provider for pure React/Next.js apps
3
3
  *
4
- * This provider is specifically for web environments and doesn't include
5
- * React Native-specific dependencies. It provides:
4
+ * Use this provider for web apps that DON'T use Expo/React Native.
5
+ * For Expo apps (native + web), use `OxyProvider` instead - it works on all platforms.
6
+ *
7
+ * Features:
6
8
  * - Automatic cross-domain SSO via FedCM (Chrome 108+, Safari 16.4+, Edge 108+)
9
+ * - No React Native dependencies
7
10
  * - Session management
8
11
  * - All useOxy/useAuth functionality
9
12
  *
10
- * Zero-config: Just wrap your app and SSO works automatically across domains.
11
- *
12
13
  * Usage:
13
14
  * ```tsx
15
+ * // For pure React/Next.js apps (no Expo):
14
16
  * import { WebOxyProvider, useAuth } from '@oxyhq/services';
15
17
  *
16
18
  * function App() {
@@ -21,11 +23,8 @@
21
23
  * );
22
24
  * }
23
25
  *
24
- * function LoginButton() {
25
- * const { isAuthenticated, signIn, user } = useAuth();
26
- * if (isAuthenticated) return <span>Welcome, {user?.username}!</span>;
27
- * return <button onClick={() => signIn()}>Sign In</button>;
28
- * }
26
+ * // For Expo apps (native + web), use OxyProvider instead:
27
+ * import { OxyProvider, useAuth } from '@oxyhq/services';
29
28
  * ```
30
29
  */
31
30
  import { type FC, type ReactNode } from 'react';
@@ -1 +1 @@
1
- {"version":3,"file":"WebOxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/WebOxyProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAA+B,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,QAAA,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA2D3C,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"WebOxyProvider.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/WebOxyProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAA+B,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;CACpD;AAED;;;;;;;;;GASG;AACH,QAAA,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA2D3C,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;CACvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,IAAI,aAAa,CAoGvC;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;CACvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,IAAI,aAAa,CAqHvC;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAI/B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CAiEpC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,UAAU,gBAAgB;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAI/B;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CA0EpC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "5.19.0",
3
+ "version": "5.20.1",
4
4
  "description": "Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -13,6 +13,11 @@ export interface FedCMConfig {
13
13
  clientId?: string;
14
14
  }
15
15
 
16
+ // Global lock to prevent concurrent FedCM requests
17
+ // FedCM only allows one navigator.credentials.get request at a time
18
+ let fedCMRequestInProgress = false;
19
+ let fedCMRequestPromise: Promise<any> | null = null;
20
+
16
21
  /**
17
22
  * Federated Credential Management (FedCM) Authentication Mixin
18
23
  *
@@ -190,6 +195,9 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
190
195
  /**
191
196
  * Request identity credential from browser using FedCM API
192
197
  *
198
+ * Uses a global lock to prevent concurrent requests, as FedCM only
199
+ * allows one navigator.credentials.get request at a time.
200
+ *
193
201
  * @private
194
202
  */
195
203
  public async requestIdentityCredential(options: {
@@ -199,34 +207,50 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
199
207
  context?: string;
200
208
  mediation?: 'silent' | 'optional' | 'required';
201
209
  }): Promise<{ token: string } | null> {
210
+ // If a request is already in progress, wait for it instead of starting a new one
211
+ if (fedCMRequestInProgress && fedCMRequestPromise) {
212
+ try {
213
+ return await fedCMRequestPromise;
214
+ } catch {
215
+ return null;
216
+ }
217
+ }
218
+
219
+ fedCMRequestInProgress = true;
202
220
  const controller = new AbortController();
203
221
  const timeout = setTimeout(() => controller.abort(), (this.constructor as any).FEDCM_TIMEOUT);
204
222
 
205
- try {
206
- // Type assertion needed as FedCM types may not be in all TypeScript versions
207
- const credential = (await (navigator.credentials as any).get({
208
- identity: {
209
- providers: [
210
- {
211
- configURL: options.configURL,
212
- clientId: options.clientId,
213
- nonce: options.nonce,
214
- ...(options.context && { loginHint: options.context }),
215
- },
216
- ],
217
- },
218
- mediation: options.mediation || 'optional',
219
- signal: controller.signal,
220
- })) as any;
221
-
222
- if (!credential || credential.type !== 'identity') {
223
- return null;
223
+ fedCMRequestPromise = (async () => {
224
+ try {
225
+ // Type assertion needed as FedCM types may not be in all TypeScript versions
226
+ const credential = (await (navigator.credentials as any).get({
227
+ identity: {
228
+ providers: [
229
+ {
230
+ configURL: options.configURL,
231
+ clientId: options.clientId,
232
+ nonce: options.nonce,
233
+ ...(options.context && { loginHint: options.context }),
234
+ },
235
+ ],
236
+ },
237
+ mediation: options.mediation || 'optional',
238
+ signal: controller.signal,
239
+ })) as any;
240
+
241
+ if (!credential || credential.type !== 'identity') {
242
+ return null;
243
+ }
244
+
245
+ return { token: credential.token };
246
+ } finally {
247
+ clearTimeout(timeout);
248
+ fedCMRequestInProgress = false;
249
+ fedCMRequestPromise = null;
224
250
  }
251
+ })();
225
252
 
226
- return { token: credential.token };
227
- } finally {
228
- clearTimeout(timeout);
229
- }
253
+ return fedCMRequestPromise;
230
254
  }
231
255
 
232
256
  /**
@@ -1,13 +1,11 @@
1
1
  import { useEffect, useRef, useState, type FC } from 'react';
2
- import { AppState } from 'react-native';
2
+ import { AppState, Platform } from 'react-native';
3
3
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
4
4
  import { SafeAreaProvider } from 'react-native-safe-area-context';
5
- import { KeyboardProvider } from 'react-native-keyboard-controller';
6
5
  import type { OxyProviderProps } from '../types/navigation';
7
6
  import { OxyContextProvider } from '../context/OxyContext';
8
7
  import { QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query';
9
8
  import { setupFonts } from './FontLoader';
10
- import BottomSheetRouter from './BottomSheetRouter';
11
9
  import { Toaster } from '../../lib/sonner';
12
10
  import { createQueryClient } from '../hooks/queryClient';
13
11
  import { createPlatformStorage, type StorageInterface } from '../utils/storageHelpers';
@@ -15,11 +13,51 @@ import { createPlatformStorage, type StorageInterface } from '../utils/storageHe
15
13
  // Initialize fonts automatically
16
14
  setupFonts();
17
15
 
16
+ // Detect if running on web
17
+ const isWeb = Platform.OS === 'web';
18
+
19
+ // Conditionally import native-only components
20
+ let KeyboardProvider: any = ({ children }: any) => children;
21
+ let BottomSheetRouter: any = () => null;
22
+
23
+ if (!isWeb) {
24
+ try {
25
+ // Only import on native platforms
26
+ KeyboardProvider = require('react-native-keyboard-controller').KeyboardProvider;
27
+ BottomSheetRouter = require('./BottomSheetRouter').default;
28
+ } catch {
29
+ // Fallback if imports fail
30
+ }
31
+ }
32
+
18
33
  /**
19
- * OxyProvider component
34
+ * OxyProvider - Universal provider for Expo apps (native + web)
35
+ *
36
+ * Zero-config authentication and session management:
37
+ * - Native (iOS/Android): Keychain-based identity, bottom sheet auth UI
38
+ * - Web: FedCM cross-domain SSO, popup fallback
20
39
  *
21
- * Provides the authentication/session context used across the app.
22
- * UI composition (e.g. OxyRouter inside a bottom sheet) can be added externally.
40
+ * Usage:
41
+ * ```tsx
42
+ * import { OxyProvider, useAuth } from '@oxyhq/services';
43
+ *
44
+ * function App() {
45
+ * return (
46
+ * <OxyProvider baseURL="https://api.oxy.so">
47
+ * <YourApp />
48
+ * </OxyProvider>
49
+ * );
50
+ * }
51
+ *
52
+ * function MyComponent() {
53
+ * const { isAuthenticated, user, signIn, signOut } = useAuth();
54
+ *
55
+ * if (!isAuthenticated) {
56
+ * return <OxySignInButton />;
57
+ * }
58
+ * return <Text>Welcome, {user?.username}!</Text>;
59
+ * }
60
+ * ```
23
61
  */
24
62
  const OxyProvider: FC<OxyProviderProps> = ({
25
63
  oxyServices,
@@ -72,14 +110,26 @@ const OxyProvider: FC<OxyProviderProps> = ({
72
110
  };
73
111
  }, [providedQueryClient]);
74
112
 
75
- // Hook React Query focus manager into React Native AppState
113
+ // Hook React Query focus manager into app state (native) or visibility (web)
76
114
  useEffect(() => {
77
- const subscription = AppState.addEventListener('change', (state) => {
78
- focusManager.setFocused(state === 'active');
79
- });
80
- return () => {
81
- subscription.remove();
82
- };
115
+ if (isWeb) {
116
+ // Web: use document visibility
117
+ const handleVisibilityChange = () => {
118
+ focusManager.setFocused(document.visibilityState === 'visible');
119
+ };
120
+ document.addEventListener('visibilitychange', handleVisibilityChange);
121
+ return () => {
122
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
123
+ };
124
+ } else {
125
+ // Native: use AppState
126
+ const subscription = AppState.addEventListener('change', (state) => {
127
+ focusManager.setFocused(state === 'active');
128
+ });
129
+ return () => {
130
+ subscription.remove();
131
+ };
132
+ }
83
133
  }, []);
84
134
 
85
135
  // Setup network status monitoring for offline detection
@@ -88,35 +138,35 @@ const OxyProvider: FC<OxyProviderProps> = ({
88
138
 
89
139
  const setupNetworkMonitoring = async () => {
90
140
  try {
91
- // For React Native, try to use NetInfo
92
- if (typeof window === 'undefined' || (typeof navigator !== 'undefined' && navigator.product === 'ReactNative')) {
141
+ if (isWeb) {
142
+ // Web: use navigator.onLine
143
+ onlineManager.setOnline(navigator.onLine);
144
+ const handleOnline = () => onlineManager.setOnline(true);
145
+ const handleOffline = () => onlineManager.setOnline(false);
146
+
147
+ window.addEventListener('online', handleOnline);
148
+ window.addEventListener('offline', handleOffline);
149
+
150
+ cleanup = () => {
151
+ window.removeEventListener('online', handleOnline);
152
+ window.removeEventListener('offline', handleOffline);
153
+ };
154
+ } else {
155
+ // Native: try to use NetInfo
93
156
  try {
94
157
  const NetInfo = await import('@react-native-community/netinfo');
95
158
  const state = await NetInfo.default.fetch();
96
159
  onlineManager.setOnline(state.isConnected ?? true);
97
-
160
+
98
161
  const unsubscribe = NetInfo.default.addEventListener((state: { isConnected: boolean | null }) => {
99
162
  onlineManager.setOnline(state.isConnected ?? true);
100
163
  });
101
-
164
+
102
165
  cleanup = () => unsubscribe();
103
166
  } catch {
104
167
  // NetInfo not available, default to online
105
168
  onlineManager.setOnline(true);
106
169
  }
107
- } else {
108
- // For web, use navigator.onLine
109
- onlineManager.setOnline(navigator.onLine);
110
- const handleOnline = () => onlineManager.setOnline(true);
111
- const handleOffline = () => onlineManager.setOnline(false);
112
-
113
- window.addEventListener('online', handleOnline);
114
- window.addEventListener('offline', handleOffline);
115
-
116
- cleanup = () => {
117
- window.removeEventListener('online', handleOnline);
118
- window.removeEventListener('offline', handleOffline);
119
- };
120
170
  }
121
171
  } catch (error) {
122
172
  // Default to online if detection fails
@@ -133,30 +183,45 @@ const OxyProvider: FC<OxyProviderProps> = ({
133
183
 
134
184
  // Ensure we have a valid QueryClient
135
185
  if (!queryClient) {
136
- // Return loading state or fallback
137
186
  return null;
138
187
  }
139
188
 
189
+ // Core content that works on all platforms
190
+ const coreContent = (
191
+ <QueryClientProvider client={queryClient}>
192
+ <OxyContextProvider
193
+ oxyServices={oxyServices as any}
194
+ baseURL={baseURL}
195
+ authWebUrl={authWebUrl}
196
+ authRedirectUri={authRedirectUri}
197
+ storageKeyPrefix={storageKeyPrefix}
198
+ onAuthStateChange={onAuthStateChange as any}
199
+ >
200
+ {children}
201
+ {/* Only render bottom sheet router on native */}
202
+ {!isWeb && <BottomSheetRouter />}
203
+ <Toaster />
204
+ </OxyContextProvider>
205
+ </QueryClientProvider>
206
+ );
207
+
208
+ // On web, minimal wrappers (GestureHandler and SafeArea work via react-native-web)
209
+ if (isWeb) {
210
+ return (
211
+ <SafeAreaProvider>
212
+ <GestureHandlerRootView style={{ flex: 1 }}>
213
+ {coreContent}
214
+ </GestureHandlerRootView>
215
+ </SafeAreaProvider>
216
+ );
217
+ }
218
+
219
+ // On native, full wrappers including KeyboardProvider
140
220
  return (
141
221
  <SafeAreaProvider>
142
222
  <GestureHandlerRootView style={{ flex: 1 }}>
143
223
  <KeyboardProvider>
144
- {queryClient && (
145
- <QueryClientProvider client={queryClient}>
146
- <OxyContextProvider
147
- oxyServices={oxyServices as any}
148
- baseURL={baseURL}
149
- authWebUrl={authWebUrl}
150
- authRedirectUri={authRedirectUri}
151
- storageKeyPrefix={storageKeyPrefix}
152
- onAuthStateChange={onAuthStateChange as any}
153
- >
154
- {children}
155
- <BottomSheetRouter />
156
- <Toaster />
157
- </OxyContextProvider>
158
- </QueryClientProvider>
159
- )}
224
+ {coreContent}
160
225
  </KeyboardProvider>
161
226
  </GestureHandlerRootView>
162
227
  </SafeAreaProvider>
@@ -1,16 +1,18 @@
1
1
  /**
2
- * WebOxyProvider - OxyProvider for web apps (Next.js, React)
2
+ * WebOxyProvider - Lightweight provider for pure React/Next.js apps
3
3
  *
4
- * This provider is specifically for web environments and doesn't include
5
- * React Native-specific dependencies. It provides:
4
+ * Use this provider for web apps that DON'T use Expo/React Native.
5
+ * For Expo apps (native + web), use `OxyProvider` instead - it works on all platforms.
6
+ *
7
+ * Features:
6
8
  * - Automatic cross-domain SSO via FedCM (Chrome 108+, Safari 16.4+, Edge 108+)
9
+ * - No React Native dependencies
7
10
  * - Session management
8
11
  * - All useOxy/useAuth functionality
9
12
  *
10
- * Zero-config: Just wrap your app and SSO works automatically across domains.
11
- *
12
13
  * Usage:
13
14
  * ```tsx
15
+ * // For pure React/Next.js apps (no Expo):
14
16
  * import { WebOxyProvider, useAuth } from '@oxyhq/services';
15
17
  *
16
18
  * function App() {
@@ -21,11 +23,8 @@
21
23
  * );
22
24
  * }
23
25
  *
24
- * function LoginButton() {
25
- * const { isAuthenticated, signIn, user } = useAuth();
26
- * if (isAuthenticated) return <span>Welcome, {user?.username}!</span>;
27
- * return <button onClick={() => signIn()}>Sign In</button>;
28
- * }
26
+ * // For Expo apps (native + web), use OxyProvider instead:
27
+ * import { OxyProvider, useAuth } from '@oxyhq/services';
29
28
  * ```
30
29
  */
31
30