@oxyhq/services 5.18.5 → 5.19.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 (42) hide show
  1. package/lib/commonjs/core/mixins/OxyServices.fedcm.js +1 -1
  2. package/lib/commonjs/core/mixins/OxyServices.fedcm.js.map +1 -1
  3. package/lib/commonjs/ui/components/OxySignInButton.js +24 -17
  4. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  5. package/lib/commonjs/ui/components/WebOxyProvider.js +11 -2
  6. package/lib/commonjs/ui/components/WebOxyProvider.js.map +1 -1
  7. package/lib/commonjs/ui/hooks/useAuth.js +33 -8
  8. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  9. package/lib/commonjs/ui/hooks/useWebSSO.js +55 -16
  10. package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
  11. package/lib/module/core/mixins/OxyServices.fedcm.js +1 -1
  12. package/lib/module/core/mixins/OxyServices.fedcm.js.map +1 -1
  13. package/lib/module/ui/components/OxySignInButton.js +24 -17
  14. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  15. package/lib/module/ui/components/WebOxyProvider.js +11 -2
  16. package/lib/module/ui/components/WebOxyProvider.js.map +1 -1
  17. package/lib/module/ui/hooks/useAuth.js +33 -8
  18. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  19. package/lib/module/ui/hooks/useWebSSO.js +55 -16
  20. package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
  21. package/lib/typescript/commonjs/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  23. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts +11 -2
  24. package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +8 -3
  26. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +29 -7
  28. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
  29. package/lib/typescript/module/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
  30. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  31. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts +11 -2
  32. package/lib/typescript/module/ui/components/WebOxyProvider.d.ts.map +1 -1
  33. package/lib/typescript/module/ui/hooks/useAuth.d.ts +8 -3
  34. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  35. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +29 -7
  36. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/core/mixins/OxyServices.fedcm.ts +1 -1
  39. package/src/ui/components/OxySignInButton.tsx +24 -17
  40. package/src/ui/components/WebOxyProvider.tsx +11 -2
  41. package/src/ui/hooks/useAuth.ts +42 -12
  42. package/src/ui/hooks/useWebSSO.ts +59 -15
@@ -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>
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * This provider is specifically for web environments and doesn't include
5
5
  * React Native-specific dependencies. It provides:
6
- * - Automatic cross-domain SSO via hidden iframe
6
+ * - Automatic cross-domain SSO via FedCM (Chrome 108+, Safari 16.4+, Edge 108+)
7
7
  * - Session management
8
8
  * - All useOxy/useAuth functionality
9
9
  *
10
+ * Zero-config: Just wrap your app and SSO works automatically across domains.
11
+ *
10
12
  * Usage:
11
13
  * ```tsx
12
14
  * import { WebOxyProvider, useAuth } from '@oxyhq/services';
@@ -18,6 +20,12 @@
18
20
  * </WebOxyProvider>
19
21
  * );
20
22
  * }
23
+ *
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
+ * }
21
29
  * ```
22
30
  */
23
31
 
@@ -40,7 +48,8 @@ export interface WebOxyProviderProps {
40
48
  * OxyProvider for web applications
41
49
  *
42
50
  * Features:
43
- * - Automatic cross-domain SSO (checks auth.oxy.so/auth/silent on mount)
51
+ * - Automatic cross-domain SSO via FedCM (browser-native identity API)
52
+ * - Works across different TLDs (alia.onl, mention.earth, homiio.com, etc.)
44
53
  * - Session persistence in localStorage
45
54
  * - TanStack Query for data fetching
46
55
  * - 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();
@@ -1,11 +1,18 @@
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
 
11
18
  import { useEffect, useRef, useCallback } from 'react';
@@ -15,78 +22,115 @@ import type { SessionLoginResponse } from '../../models/session';
15
22
  interface UseWebSSOOptions {
16
23
  oxyServices: OxyServices;
17
24
  onSessionFound: (session: SessionLoginResponse) => Promise<void>;
25
+ onSSOUnavailable?: () => void;
18
26
  onError?: (error: Error) => void;
19
27
  enabled?: boolean;
20
28
  }
21
29
 
22
30
  interface UseWebSSOResult {
31
+ /** Manually trigger SSO check */
23
32
  checkSSO: () => Promise<SessionLoginResponse | null>;
33
+ /** Whether SSO check is in progress */
24
34
  isChecking: boolean;
35
+ /** Whether FedCM is supported in this browser */
36
+ isFedCMSupported: boolean;
25
37
  }
26
38
 
27
39
  /**
28
40
  * Check if we're running in a web browser environment (not React Native)
29
41
  */
30
42
  function isWebBrowser(): boolean {
31
- // Check for browser globals and that we have a real DOM (React Native has window but not documentElement)
32
43
  return typeof window !== 'undefined' &&
33
44
  typeof document !== 'undefined' &&
34
45
  typeof document.documentElement !== 'undefined';
35
46
  }
36
47
 
37
48
  /**
38
- * Hook for automatic web SSO
49
+ * Hook for automatic cross-domain web SSO
39
50
  *
40
- * Automatically checks for existing cross-domain session on mount.
41
- * Only runs on web platforms. Uses OxyServices.silentSignIn() internally.
51
+ * Uses FedCM (Federated Credential Management) - the modern browser-native
52
+ * identity federation API. This is the same technology that powers
53
+ * Google's cross-domain SSO (YouTube, Gmail, Maps, etc.).
54
+ *
55
+ * Key benefits:
56
+ * - Works across different TLDs (alia.onl ↔ mention.earth ↔ homiio.com)
57
+ * - No third-party cookies required
58
+ * - Privacy-preserving (browser mediates identity, IdP can't track)
59
+ * - Automatic silent sign-in after initial authentication
60
+ *
61
+ * For browsers without FedCM (Firefox, older browsers), automatic SSO
62
+ * is not possible. Users will see a sign-in button instead.
42
63
  */
43
64
  export function useWebSSO({
44
65
  oxyServices,
45
66
  onSessionFound,
67
+ onSSOUnavailable,
46
68
  onError,
47
69
  enabled = true,
48
70
  }: UseWebSSOOptions): UseWebSSOResult {
49
71
  const isCheckingRef = useRef(false);
50
72
  const hasCheckedRef = useRef(false);
51
73
 
74
+ // Check FedCM support once
75
+ const fedCMSupported = isWebBrowser() && (oxyServices as any).isFedCMSupported?.();
76
+
52
77
  const checkSSO = useCallback(async (): Promise<SessionLoginResponse | null> => {
53
78
  if (!isWebBrowser() || isCheckingRef.current) {
54
79
  return null;
55
80
  }
56
81
 
82
+ // FedCM is the only reliable cross-domain SSO mechanism
83
+ // Third-party cookies are deprecated and unreliable
84
+ if (!fedCMSupported) {
85
+ onSSOUnavailable?.();
86
+ return null;
87
+ }
88
+
57
89
  isCheckingRef.current = true;
58
90
 
59
91
  try {
60
- // Use the existing silentSignIn method from OxyServices
61
- // which handles iframe creation, postMessage, and token storage
62
- const session = await (oxyServices as any).silentSignIn?.();
92
+ // Use FedCM for cross-domain SSO
93
+ // This works because browser treats IdP requests as first-party
94
+ const session = await (oxyServices as any).silentSignInWithFedCM?.();
63
95
 
64
96
  if (session) {
65
97
  await onSessionFound(session);
98
+ return session;
66
99
  }
67
100
 
68
- return session;
101
+ // No session found - user needs to sign in
102
+ onSSOUnavailable?.();
103
+ return null;
69
104
  } catch (error) {
105
+ // FedCM failed - could be network error, user not signed in, etc.
106
+ onSSOUnavailable?.();
70
107
  onError?.(error instanceof Error ? error : new Error(String(error)));
71
108
  return null;
72
109
  } finally {
73
110
  isCheckingRef.current = false;
74
111
  }
75
- }, [oxyServices, onSessionFound, onError]);
112
+ }, [oxyServices, onSessionFound, onSSOUnavailable, onError, fedCMSupported]);
76
113
 
77
- // Auto-check SSO on mount (web only)
114
+ // Auto-check SSO on mount (web only, FedCM only)
78
115
  useEffect(() => {
79
116
  if (!enabled || !isWebBrowser() || hasCheckedRef.current) {
80
117
  return;
81
118
  }
82
119
 
83
120
  hasCheckedRef.current = true;
84
- checkSSO();
85
- }, [enabled, checkSSO]);
121
+
122
+ if (fedCMSupported) {
123
+ checkSSO();
124
+ } else {
125
+ // Browser doesn't support FedCM - notify caller
126
+ onSSOUnavailable?.();
127
+ }
128
+ }, [enabled, checkSSO, fedCMSupported, onSSOUnavailable]);
86
129
 
87
130
  return {
88
131
  checkSSO,
89
132
  isChecking: isCheckingRef.current,
133
+ isFedCMSupported: fedCMSupported,
90
134
  };
91
135
  }
92
136