@oxyhq/services 5.22.0 → 5.23.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.
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Web-Only Oxy Context
3
+ * Clean implementation with ZERO React Native dependencies
4
+ */
5
+
6
+ import {
7
+ createContext,
8
+ useCallback,
9
+ useContext,
10
+ useEffect,
11
+ useState,
12
+ type ReactNode,
13
+ } from 'react';
14
+ import { OxyServices } from '../core';
15
+ import type { User } from '../models/interfaces';
16
+ import type { ClientSession } from '../models/session';
17
+ import { QueryClientProvider } from '@tanstack/react-query';
18
+ import { createQueryClient } from '../ui/hooks/queryClient';
19
+
20
+ export interface WebAuthState {
21
+ user: User | null;
22
+ isAuthenticated: boolean;
23
+ isLoading: boolean;
24
+ error: string | null;
25
+ }
26
+
27
+ export interface WebAuthActions {
28
+ signIn: () => Promise<void>;
29
+ signOut: () => Promise<void>;
30
+ }
31
+
32
+ export interface WebOxyContextValue extends WebAuthState, WebAuthActions {
33
+ oxyServices: OxyServices;
34
+ }
35
+
36
+ const WebOxyContext = createContext<WebOxyContextValue | null>(null);
37
+
38
+ export interface WebOxyProviderProps {
39
+ children: ReactNode;
40
+ baseURL: string;
41
+ authWebUrl?: string;
42
+ onAuthStateChange?: (user: User | null) => void;
43
+ }
44
+
45
+ /**
46
+ * Web-only Oxy Provider
47
+ * Minimal, clean implementation for web apps
48
+ */
49
+ export function WebOxyProvider({
50
+ children,
51
+ baseURL,
52
+ authWebUrl,
53
+ onAuthStateChange,
54
+ }: WebOxyProviderProps) {
55
+ const [oxyServices] = useState(() => new OxyServices({ baseURL, authWebUrl }));
56
+ const [user, setUser] = useState<User | null>(null);
57
+ const [isLoading, setIsLoading] = useState(true);
58
+ const [error, setError] = useState<string | null>(null);
59
+ const [queryClient] = useState(() => createQueryClient());
60
+
61
+ const isAuthenticated = !!user;
62
+
63
+ // Initialize - check for existing session
64
+ useEffect(() => {
65
+ let mounted = true;
66
+
67
+ const initAuth = async () => {
68
+ try {
69
+ // Try to get sessions from storage (localStorage)
70
+ const sessions = await oxyServices.getAllSessions();
71
+
72
+ if (sessions && sessions.length > 0) {
73
+ const activeSession = sessions.find((s: ClientSession) => s.isCurrent) || sessions[0];
74
+
75
+ // Try to get user info from the active session's userId
76
+ if (activeSession && activeSession.userId) {
77
+ try {
78
+ const userProfile = await oxyServices.getProfile(activeSession.userId);
79
+ if (mounted && userProfile) {
80
+ setUser(userProfile as User);
81
+ setIsLoading(false);
82
+ return;
83
+ }
84
+ } catch (err) {
85
+ console.log('[WebOxy] Could not fetch user profile:', err);
86
+ }
87
+ }
88
+ }
89
+
90
+ // No active session - check for cross-domain SSO via FedCM
91
+ if (typeof window !== 'undefined' && 'IdentityCredential' in window) {
92
+ try {
93
+ const credential = await navigator.credentials.get({
94
+ // @ts-expect-error - FedCM identity property is not in standard types
95
+ identity: {
96
+ providers: [{
97
+ configURL: `${authWebUrl || 'https://auth.oxy.so'}/fedcm.json`,
98
+ clientId: window.location.origin,
99
+ }]
100
+ }
101
+ });
102
+
103
+ if (credential && 'token' in credential) {
104
+ // Use the token to authenticate
105
+ const session = await oxyServices.authenticateWithToken(credential.token);
106
+ if (mounted && session.user) {
107
+ setUser(session.user);
108
+ }
109
+ }
110
+ } catch (fedcmError) {
111
+ // FedCM not available or user cancelled - this is fine
112
+ console.log('[WebOxy] FedCM SSO not available:', fedcmError);
113
+ }
114
+ }
115
+ } catch (err) {
116
+ console.error('[WebOxy] Init error:', err);
117
+ if (mounted) {
118
+ setError(err instanceof Error ? err.message : 'Failed to initialize auth');
119
+ }
120
+ } finally {
121
+ if (mounted) {
122
+ setIsLoading(false);
123
+ }
124
+ }
125
+ };
126
+
127
+ initAuth();
128
+
129
+ return () => {
130
+ mounted = false;
131
+ };
132
+ }, [oxyServices, authWebUrl]);
133
+
134
+ // Notify parent of auth state changes
135
+ useEffect(() => {
136
+ onAuthStateChange?.(user);
137
+ }, [user, onAuthStateChange]);
138
+
139
+ const signIn = useCallback(async () => {
140
+ setError(null);
141
+ setIsLoading(true);
142
+
143
+ try {
144
+ // Open popup to auth.oxy.so
145
+ const popup = window.open(
146
+ `${authWebUrl || 'https://auth.oxy.so'}/login?origin=${encodeURIComponent(window.location.origin)}`,
147
+ 'oxy-auth',
148
+ 'width=500,height=700,popup=yes'
149
+ );
150
+
151
+ if (!popup) {
152
+ throw new Error('Popup blocked. Please allow popups for this site.');
153
+ }
154
+
155
+ // Listen for message from popup
156
+ const handleMessage = async (event: MessageEvent) => {
157
+ if (event.origin !== (authWebUrl || 'https://auth.oxy.so')) {
158
+ return;
159
+ }
160
+
161
+ if (event.data.type === 'oxy-auth-success') {
162
+ const { sessionId, accessToken, user: authUser } = event.data;
163
+
164
+ // Store session (note: user property doesn't exist in ClientSession type)
165
+ const session: ClientSession = {
166
+ sessionId,
167
+ deviceId: event.data.deviceId || '',
168
+ expiresAt: event.data.expiresAt || '',
169
+ lastActive: new Date().toISOString(),
170
+ userId: authUser.id,
171
+ isCurrent: true,
172
+ };
173
+ await oxyServices.storeSession(session);
174
+
175
+ setUser(authUser);
176
+ setIsLoading(false);
177
+
178
+ window.removeEventListener('message', handleMessage);
179
+ popup.close();
180
+ } else if (event.data.type === 'oxy-auth-error') {
181
+ setError(event.data.error || 'Authentication failed');
182
+ setIsLoading(false);
183
+ window.removeEventListener('message', handleMessage);
184
+ popup.close();
185
+ }
186
+ };
187
+
188
+ window.addEventListener('message', handleMessage);
189
+
190
+ // Check if popup was closed
191
+ const checkClosed = setInterval(() => {
192
+ if (popup.closed) {
193
+ clearInterval(checkClosed);
194
+ window.removeEventListener('message', handleMessage);
195
+ setIsLoading(false);
196
+ }
197
+ }, 500);
198
+ } catch (err) {
199
+ console.error('[WebOxy] Sign in error:', err);
200
+ setError(err instanceof Error ? err.message : 'Sign in failed');
201
+ setIsLoading(false);
202
+ }
203
+ }, [oxyServices, authWebUrl]);
204
+
205
+ const signOut = useCallback(async () => {
206
+ setError(null);
207
+
208
+ try {
209
+ await oxyServices.logout();
210
+ setUser(null);
211
+ } catch (err) {
212
+ console.error('[WebOxy] Sign out error:', err);
213
+ setError(err instanceof Error ? err.message : 'Sign out failed');
214
+ }
215
+ }, [oxyServices]);
216
+
217
+ const contextValue: WebOxyContextValue = {
218
+ user,
219
+ isAuthenticated,
220
+ isLoading,
221
+ error,
222
+ signIn,
223
+ signOut,
224
+ oxyServices,
225
+ };
226
+
227
+ return (
228
+ <QueryClientProvider client={queryClient}>
229
+ <WebOxyContext.Provider value={contextValue}>
230
+ {children}
231
+ </WebOxyContext.Provider>
232
+ </QueryClientProvider>
233
+ );
234
+ }
235
+
236
+ export function useWebOxy() {
237
+ const context = useContext(WebOxyContext);
238
+ if (!context) {
239
+ throw new Error('useWebOxy must be used within WebOxyProvider');
240
+ }
241
+ return context;
242
+ }
243
+
244
+ export function useAuth() {
245
+ const { user, isAuthenticated, isLoading, error, signIn, signOut, oxyServices } = useWebOxy();
246
+
247
+ return {
248
+ user,
249
+ isAuthenticated,
250
+ isLoading,
251
+ isReady: !isLoading,
252
+ error,
253
+ signIn,
254
+ signOut,
255
+ oxyServices,
256
+ };
257
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @oxyhq/services/web - Web-Only Module
3
+ *
4
+ * Clean, professional web module with ZERO React Native dependencies.
5
+ * Perfect for Next.js, Vite, Create React App, and other pure React web apps.
6
+ *
7
+ * Features:
8
+ * - WebOxyProvider for React context
9
+ * - useAuth hook for authentication
10
+ * - Cross-domain SSO via FedCM
11
+ * - Full TypeScript support
12
+ * - Zero bundler configuration needed
13
+ *
14
+ * Usage:
15
+ * ```tsx
16
+ * import { WebOxyProvider, useAuth } from '@oxyhq/services/web';
17
+ *
18
+ * function App() {
19
+ * return (
20
+ * <WebOxyProvider baseURL="https://api.oxy.so">
21
+ * <YourApp />
22
+ * </WebOxyProvider>
23
+ * );
24
+ * }
25
+ *
26
+ * function YourApp() {
27
+ * const { user, isAuthenticated, signIn, signOut } = useAuth();
28
+ * // ... your app logic
29
+ * }
30
+ * ```
31
+ */
32
+
33
+ // ==================== Core API ====================
34
+ // Re-export core services (zero React Native deps)
35
+ export {
36
+ OxyServices,
37
+ OxyAuthenticationError,
38
+ OxyAuthenticationTimeoutError,
39
+ OXY_CLOUD_URL,
40
+ oxyClient,
41
+ CrossDomainAuth,
42
+ createCrossDomainAuth,
43
+ DeviceManager,
44
+ } from '../core';
45
+
46
+ export type {
47
+ CrossDomainAuthOptions,
48
+ DeviceFingerprint,
49
+ StoredDeviceInfo,
50
+ } from '../core';
51
+
52
+ // ==================== Web Components ====================
53
+ export { WebOxyProvider, useWebOxy, useAuth } from './WebOxyContext';
54
+ export type { WebOxyProviderProps, WebAuthState, WebAuthActions, WebOxyContextValue } from './WebOxyContext';
55
+
56
+ // ==================== Models & Types ====================
57
+ // Re-export commonly used types
58
+ export type {
59
+ User,
60
+ ApiError,
61
+ Notification,
62
+ FileMetadata,
63
+ AssetUploadProgress,
64
+ PaymentMethod,
65
+ KarmaLeaderboardEntry,
66
+ KarmaRule,
67
+ Transaction,
68
+ DeviceSession,
69
+ } from '../models/interfaces';
70
+
71
+ // Re-export session types
72
+ export type { ClientSession } from '../models/session';
73
+
74
+ // ==================== Utilities ====================
75
+ export {
76
+ SUPPORTED_LANGUAGES,
77
+ getLanguageMetadata,
78
+ getLanguageName,
79
+ getNativeLanguageName,
80
+ normalizeLanguageCode,
81
+ } from '../utils/languageUtils';
82
+
83
+ export type { LanguageMetadata } from '../utils/languageUtils';
84
+
85
+ // Default export for convenience
86
+ import { WebOxyProvider } from './WebOxyContext';
87
+ export default WebOxyProvider;
package/src/web.ts CHANGED
@@ -200,9 +200,9 @@ export type { HandleApiErrorOptions } from './ui/utils/authHelpers';
200
200
  export { useFileFiltering } from './ui/hooks/useFileFiltering';
201
201
  export type { ViewMode, SortBy, SortOrder } from './ui/hooks/useFileFiltering';
202
202
 
203
- // Web-compatible UI components (no React Native dependencies)
204
- export { OxySignInButton } from './ui/components/OxySignInButton';
205
- export { OxyLogo } from './ui/components/OxyLogo';
203
+ // Note: UI components like OxySignInButton and OxyLogo use React Native
204
+ // and are not included in the /web entry point. For pure web apps, use the
205
+ // useAuth hook and create your own sign-in button.
206
206
 
207
207
  // Utilities
208
208
  export * from './utils/apiUtils';
@@ -217,7 +217,6 @@ export * from './utils/validationUtils';
217
217
  export {
218
218
  logger,
219
219
  LogLevel,
220
- LogContext,
221
220
  logAuth,
222
221
  logApi,
223
222
  logSession,
@@ -226,5 +225,6 @@ export {
226
225
  logPayment,
227
226
  logPerformance
228
227
  } from './utils/loggerUtils';
228
+ export type { LogContext } from './utils/loggerUtils';
229
229
  export * from './utils/asyncUtils';
230
230
  export * from './utils/hookUtils';