@oxyhq/services 5.18.5 → 5.20.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 (52) hide show
  1. package/README.md +51 -42
  2. package/lib/commonjs/core/mixins/OxyServices.fedcm.js +1 -1
  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/OxySignInButton.js +24 -17
  7. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  8. package/lib/commonjs/ui/components/WebOxyProvider.js +13 -5
  9. package/lib/commonjs/ui/components/WebOxyProvider.js.map +1 -1
  10. package/lib/commonjs/ui/hooks/useAuth.js +33 -8
  11. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  12. package/lib/commonjs/ui/hooks/useWebSSO.js +55 -16
  13. package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
  14. package/lib/module/core/mixins/OxyServices.fedcm.js +1 -1
  15. package/lib/module/core/mixins/OxyServices.fedcm.js.map +1 -1
  16. package/lib/module/ui/components/OxyProvider.js +106 -39
  17. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  18. package/lib/module/ui/components/OxySignInButton.js +24 -17
  19. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  20. package/lib/module/ui/components/WebOxyProvider.js +13 -5
  21. package/lib/module/ui/components/WebOxyProvider.js.map +1 -1
  22. package/lib/module/ui/hooks/useAuth.js +33 -8
  23. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  24. package/lib/module/ui/hooks/useWebSSO.js +55 -16
  25. package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
  26. package/lib/typescript/commonjs/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +26 -3
  28. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts +13 -5
  31. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +8 -3
  33. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +29 -7
  35. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
  36. package/lib/typescript/module/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
  37. package/lib/typescript/module/ui/components/OxyProvider.d.ts +26 -3
  38. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  39. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  40. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts +13 -5
  41. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts.map +1 -1
  42. package/lib/typescript/module/ui/hooks/useAuth.d.ts +8 -3
  43. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  44. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +29 -7
  45. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/src/core/mixins/OxyServices.fedcm.ts +1 -1
  48. package/src/ui/components/OxyProvider.tsx +112 -47
  49. package/src/ui/components/OxySignInButton.tsx +24 -17
  50. package/src/ui/components/WebOxyProvider.tsx +13 -5
  51. package/src/ui/hooks/useAuth.ts +42 -12
  52. package/src/ui/hooks/useWebSSO.ts +59 -15
@@ -16,6 +16,11 @@
16
16
  * return <Welcome user={user} />;
17
17
  * }
18
18
  * ```
19
+ *
20
+ * Cross-domain SSO:
21
+ * - Web: Automatic via FedCM (Chrome 108+, Safari 16.4+)
22
+ * - Native: Automatic via shared Keychain/Account Manager
23
+ * - Manual sign-in: signIn() opens popup (web) or auth sheet (native)
19
24
  */
20
25
  import { useOxy } from '../context/OxyContext';
21
26
  import type { User } from '../../models/interfaces';
@@ -33,9 +38,9 @@ export interface AuthState {
33
38
  }
34
39
  export interface AuthActions {
35
40
  /**
36
- * Sign in with cryptographic identity
37
- * On native: Uses device keychain
38
- * On web: Opens auth popup/redirect
41
+ * Sign in
42
+ * - Web: Opens popup to auth.oxy.so (no public key needed)
43
+ * - Native: Uses cryptographic identity from keychain
39
44
  */
40
45
  signIn: (publicKey?: string) => Promise<User>;
41
46
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,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,CA4EvC;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,CAoGvC;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,34 +1,56 @@
1
1
  /**
2
2
  * Web SSO Hook
3
3
  *
4
- * Automatically handles cross-domain SSO for web apps.
5
- * Uses the OxyServices.silentSignIn() method which loads a hidden iframe
6
- * to check for existing session at auth.oxy.so.
4
+ * Handles cross-domain SSO for web apps using FedCM (Federated Credential Management).
5
+ *
6
+ * FedCM is the modern, privacy-preserving standard for cross-domain identity federation.
7
+ * It works across completely different TLDs (alia.onl, mention.earth, homiio.com, etc.)
8
+ * without relying on third-party cookies.
9
+ *
10
+ * For browsers without FedCM support, users will need to click a sign-in button
11
+ * which triggers a popup-based authentication flow.
7
12
  *
8
13
  * This is called automatically by OxyContext on web platforms.
14
+ *
15
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/FedCM_API
9
16
  */
10
17
  import type { OxyServices } from '../../core/OxyServices';
11
18
  import type { SessionLoginResponse } from '../../models/session';
12
19
  interface UseWebSSOOptions {
13
20
  oxyServices: OxyServices;
14
21
  onSessionFound: (session: SessionLoginResponse) => Promise<void>;
22
+ onSSOUnavailable?: () => void;
15
23
  onError?: (error: Error) => void;
16
24
  enabled?: boolean;
17
25
  }
18
26
  interface UseWebSSOResult {
27
+ /** Manually trigger SSO check */
19
28
  checkSSO: () => Promise<SessionLoginResponse | null>;
29
+ /** Whether SSO check is in progress */
20
30
  isChecking: boolean;
31
+ /** Whether FedCM is supported in this browser */
32
+ isFedCMSupported: boolean;
21
33
  }
22
34
  /**
23
35
  * Check if we're running in a web browser environment (not React Native)
24
36
  */
25
37
  declare function isWebBrowser(): boolean;
26
38
  /**
27
- * Hook for automatic web SSO
39
+ * Hook for automatic cross-domain web SSO
40
+ *
41
+ * Uses FedCM (Federated Credential Management) - the modern browser-native
42
+ * identity federation API. This is the same technology that powers
43
+ * Google's cross-domain SSO (YouTube, Gmail, Maps, etc.).
44
+ *
45
+ * Key benefits:
46
+ * - Works across different TLDs (alia.onl ↔ mention.earth ↔ homiio.com)
47
+ * - No third-party cookies required
48
+ * - Privacy-preserving (browser mediates identity, IdP can't track)
49
+ * - Automatic silent sign-in after initial authentication
28
50
  *
29
- * Automatically checks for existing cross-domain session on mount.
30
- * Only runs on web platforms. Uses OxyServices.silentSignIn() internally.
51
+ * For browsers without FedCM (Firefox, older browsers), automatic SSO
52
+ * is not possible. Users will see a sign-in button instead.
31
53
  */
32
- export declare function useWebSSO({ oxyServices, onSessionFound, onError, enabled, }: UseWebSSOOptions): UseWebSSOResult;
54
+ export declare function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onError, enabled, }: UseWebSSOOptions): UseWebSSOResult;
33
55
  export { isWebBrowser };
34
56
  //# sourceMappingURL=useWebSSO.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useWebSSO.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useWebSSO.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;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,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrD,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,iBAAS,YAAY,IAAI,OAAO,CAK/B;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,cAAc,EACd,OAAO,EACP,OAAc,GACf,EAAE,gBAAgB,GAAG,eAAe,CA2CpC;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;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "5.18.5",
3
+ "version": "5.20.0",
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",
@@ -240,7 +240,7 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
240
240
  public async exchangeIdTokenForSession(idToken: string): Promise<SessionLoginResponse> {
241
241
  return this.makeRequest<SessionLoginResponse>(
242
242
  'POST',
243
- '/api/auth/fedcm/exchange',
243
+ '/api/fedcm/exchange',
244
244
  { id_token: idToken },
245
245
  { cache: false }
246
246
  );
@@ -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,6 +1,7 @@
1
1
  import type React from 'react';
2
+ import { useState } from 'react';
2
3
  import { TouchableOpacity, Text, View, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, Platform } from 'react-native';
3
- import { useOxy } from '../context/OxyContext';
4
+ import { useAuth } from '../hooks/useAuth';
4
5
  import OxyLogo from './OxyLogo';
5
6
 
6
7
  export interface OxySignInButtonProps {
@@ -79,31 +80,37 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
79
80
  disabled = false,
80
81
  showWhenAuthenticated = false,
81
82
  }) => {
82
- // Get all needed values from context in a single call
83
- const { isAuthenticated, showBottomSheet } = useOxy();
83
+ const { isAuthenticated, signIn, isLoading } = useAuth();
84
+ const [isSigningIn, setIsSigningIn] = useState(false);
84
85
 
85
86
  // Don't show the button if already authenticated (unless explicitly overridden)
86
87
  if (isAuthenticated && !showWhenAuthenticated) return null;
87
88
 
88
- // Default handler that uses the context methods
89
- const handlePress = () => {
89
+ // Default handler that uses useAuth's signIn method
90
+ // This works for both web (popup) and native (bottom sheet)
91
+ const handlePress = async () => {
90
92
  if (onPress) {
91
93
  onPress();
92
94
  return;
93
95
  }
94
96
 
95
- // Use the new bottom sheet system to show the OxyAuth screen
96
- if (showBottomSheet) {
97
- showBottomSheet('OxyAuth');
98
- } else {
97
+ if (isSigningIn) return;
98
+
99
+ setIsSigningIn(true);
100
+ try {
101
+ await signIn();
102
+ } catch (error) {
103
+ // Sign-in handled by the auth flow
99
104
  if (__DEV__) {
100
- console.warn(
101
- `OxySignInButton: showBottomSheet is not available. Make sure OxyProvider is set up correctly.`
102
- );
105
+ console.log('OxySignInButton: Sign-in flow initiated', error);
103
106
  }
107
+ } finally {
108
+ setIsSigningIn(false);
104
109
  }
105
110
  };
106
111
 
112
+ const isButtonDisabled = disabled || isLoading || isSigningIn;
113
+
107
114
  // Determine the button style based on the variant
108
115
  const getButtonStyle = () => {
109
116
  switch (variant) {
@@ -130,9 +137,9 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
130
137
 
131
138
  return (
132
139
  <TouchableOpacity
133
- style={[styles.button, getButtonStyle(), disabled && styles.buttonDisabled]}
140
+ style={[styles.button, getButtonStyle(), isButtonDisabled && styles.buttonDisabled]}
134
141
  onPress={handlePress}
135
- disabled={disabled}
142
+ disabled={isButtonDisabled}
136
143
  >
137
144
  <View style={styles.buttonContent}>
138
145
  <OxyLogo
@@ -140,10 +147,10 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
140
147
  height={20}
141
148
  fillColor={variant === 'contained' ? 'white' : '#d169e5'}
142
149
  secondaryFillColor={variant === 'contained' ? '#d169e5' : undefined}
143
- style={disabled ? { opacity: 0.6 } : undefined}
150
+ style={isButtonDisabled ? { opacity: 0.6 } : undefined}
144
151
  />
145
- <Text style={[styles.text, getTextStyle(), disabled && styles.textDisabled]}>
146
- {text}
152
+ <Text style={[styles.text, getTextStyle(), isButtonDisabled && styles.textDisabled]}>
153
+ {isSigningIn ? 'Signing in...' : text}
147
154
  </Text>
148
155
  </View>
149
156
  </TouchableOpacity>
@@ -1,14 +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:
6
- * - Automatic cross-domain SSO via hidden iframe
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:
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
13
  * Usage:
11
14
  * ```tsx
15
+ * // For pure React/Next.js apps (no Expo):
12
16
  * import { WebOxyProvider, useAuth } from '@oxyhq/services';
13
17
  *
14
18
  * function App() {
@@ -18,6 +22,9 @@
18
22
  * </WebOxyProvider>
19
23
  * );
20
24
  * }
25
+ *
26
+ * // For Expo apps (native + web), use OxyProvider instead:
27
+ * import { OxyProvider, useAuth } from '@oxyhq/services';
21
28
  * ```
22
29
  */
23
30
 
@@ -40,7 +47,8 @@ export interface WebOxyProviderProps {
40
47
  * OxyProvider for web applications
41
48
  *
42
49
  * Features:
43
- * - Automatic cross-domain SSO (checks auth.oxy.so/auth/silent on mount)
50
+ * - Automatic cross-domain SSO via FedCM (browser-native identity API)
51
+ * - Works across different TLDs (alia.onl, mention.earth, homiio.com, etc.)
44
52
  * - Session persistence in localStorage
45
53
  * - TanStack Query for data fetching
46
54
  * - No React Native dependencies
@@ -16,11 +16,17 @@
16
16
  * return <Welcome user={user} />;
17
17
  * }
18
18
  * ```
19
+ *
20
+ * Cross-domain SSO:
21
+ * - Web: Automatic via FedCM (Chrome 108+, Safari 16.4+)
22
+ * - Native: Automatic via shared Keychain/Account Manager
23
+ * - Manual sign-in: signIn() opens popup (web) or auth sheet (native)
19
24
  */
20
25
 
21
- import { useCallback } from 'react';
26
+ import { useCallback, useState } from 'react';
22
27
  import { useOxy } from '../context/OxyContext';
23
28
  import type { User } from '../../models/interfaces';
29
+ import { isWebBrowser } from './useWebSSO';
24
30
 
25
31
  export interface AuthState {
26
32
  /** Current authenticated user, null if not authenticated */
@@ -41,9 +47,9 @@ export interface AuthState {
41
47
 
42
48
  export interface AuthActions {
43
49
  /**
44
- * Sign in with cryptographic identity
45
- * On native: Uses device keychain
46
- * On web: Opens auth popup/redirect
50
+ * Sign in
51
+ * - Web: Opens popup to auth.oxy.so (no public key needed)
52
+ * - Native: Uses cryptographic identity from keychain
47
53
  */
48
54
  signIn: (publicKey?: string) => Promise<User>;
49
55
 
@@ -95,6 +101,35 @@ export function useAuth(): UseAuthReturn {
95
101
  } = useOxy();
96
102
 
97
103
  const signIn = useCallback(async (publicKey?: string): Promise<User> => {
104
+ // Web: Use popup-based authentication
105
+ if (isWebBrowser() && !publicKey) {
106
+ try {
107
+ // Try FedCM first (instant if user already signed in)
108
+ if ((oxyServices as any).isFedCMSupported?.()) {
109
+ const fedcmSession = await (oxyServices as any).signInWithFedCM?.();
110
+ if (fedcmSession?.user) {
111
+ return fedcmSession.user;
112
+ }
113
+ }
114
+
115
+ // Fallback to popup (opens auth.oxy.so in popup window)
116
+ const popupSession = await (oxyServices as any).signInWithPopup?.();
117
+ if (popupSession?.user) {
118
+ return popupSession.user;
119
+ }
120
+
121
+ throw new Error('Sign-in failed');
122
+ } catch (error) {
123
+ // If popup blocked or FedCM failed, suggest redirect
124
+ throw new Error(
125
+ error instanceof Error && error.message.includes('blocked')
126
+ ? 'Popup blocked. Please allow popups or try again.'
127
+ : 'Sign-in failed. Please try again.'
128
+ );
129
+ }
130
+ }
131
+
132
+ // Native: Use cryptographic identity
98
133
  // If public key provided, use it directly
99
134
  if (publicKey) {
100
135
  return oxySignIn(publicKey);
@@ -110,19 +145,14 @@ export function useAuth(): UseAuthReturn {
110
145
  }
111
146
  }
112
147
 
113
- // No identity - show auth UI
114
- // On native: shows bottom sheet for identity creation
115
- // On web: could trigger popup auth
148
+ // No identity - show auth UI (native bottom sheet)
116
149
  showBottomSheet?.('OxyAuth');
117
150
 
118
151
  // Return a promise that resolves when auth completes
119
- // This is a simplified version - real implementation would
120
- // wait for the auth flow to complete
121
- return new Promise((resolve, reject) => {
122
- // For now, just reject - the bottom sheet handles the flow
152
+ return new Promise((_, reject) => {
123
153
  reject(new Error('Please complete sign-in in the auth sheet'));
124
154
  });
125
- }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet]);
155
+ }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet, oxyServices]);
126
156
 
127
157
  const signOut = useCallback(async (): Promise<void> => {
128
158
  await logout();