@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.
- package/lib/commonjs/web/WebOxyContext.js +224 -0
- package/lib/commonjs/web/WebOxyContext.js.map +1 -0
- package/lib/commonjs/web/index.js +146 -0
- package/lib/commonjs/web/index.js.map +1 -0
- package/lib/commonjs/web.js +0 -23
- package/lib/commonjs/web.js.map +1 -1
- package/lib/module/web/WebOxyContext.js +218 -0
- package/lib/module/web/WebOxyContext.js.map +1 -0
- package/lib/module/web/index.js +51 -0
- package/lib/module/web/index.js.map +1 -0
- package/lib/module/web.js +4 -4
- package/lib/module/web.js.map +1 -1
- package/lib/typescript/commonjs/web/WebOxyContext.d.ts +43 -0
- package/lib/typescript/commonjs/web/WebOxyContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/web/index.d.ts +42 -0
- package/lib/typescript/commonjs/web/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/web.d.ts +2 -3
- package/lib/typescript/commonjs/web.d.ts.map +1 -1
- package/lib/typescript/module/web/WebOxyContext.d.ts +43 -0
- package/lib/typescript/module/web/WebOxyContext.d.ts.map +1 -0
- package/lib/typescript/module/web/index.d.ts +42 -0
- package/lib/typescript/module/web/index.d.ts.map +1 -0
- package/lib/typescript/module/web.d.ts +2 -3
- package/lib/typescript/module/web.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/web/WebOxyContext.tsx +257 -0
- package/src/web/index.ts +87 -0
- package/src/web.ts +4 -4
|
@@ -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
|
+
}
|
package/src/web/index.ts
ADDED
|
@@ -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
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
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';
|