@oxyhq/services 5.5.0 → 5.5.2
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/core/index.js +8 -0
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +15 -19
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/index.js +7 -0
- package/lib/commonjs/ui/hooks/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuthFetch.js +197 -0
- package/lib/commonjs/ui/hooks/useAuthFetch.js.map +1 -0
- package/lib/commonjs/ui/index.js +8 -0
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/module/core/index.js +8 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +4 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/hooks/index.js +1 -0
- package/lib/module/ui/hooks/index.js.map +1 -1
- package/lib/module/ui/hooks/useAuthFetch.js +192 -0
- package/lib/module/ui/hooks/useAuthFetch.js.map +1 -0
- package/lib/module/ui/index.js +1 -0
- package/lib/module/ui/index.js.map +1 -1
- package/lib/typescript/core/index.d.ts +5 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +3 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/index.d.ts +1 -0
- package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useAuthFetch.d.ts +33 -0
- package/lib/typescript/ui/hooks/useAuthFetch.d.ts.map +1 -0
- package/lib/typescript/ui/index.d.ts +1 -0
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/index.ts +8 -0
- package/src/index.ts +4 -4
- package/src/ui/hooks/index.ts +2 -1
- package/src/ui/hooks/useAuthFetch.ts +215 -0
- package/src/ui/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.2",
|
|
4
4
|
"description": "Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀",
|
|
5
5
|
"main": "lib/commonjs/node/index.js",
|
|
6
6
|
"module": "lib/module/node/index.js",
|
package/src/core/index.ts
CHANGED
|
@@ -191,6 +191,14 @@ export class OxyServices {
|
|
|
191
191
|
return this.accessToken !== null;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Get the current access token
|
|
196
|
+
* @returns Current access token or null if not authenticated
|
|
197
|
+
*/
|
|
198
|
+
public getAccessToken(): string | null {
|
|
199
|
+
return this.accessToken;
|
|
200
|
+
}
|
|
201
|
+
|
|
194
202
|
/**
|
|
195
203
|
* Sets authentication tokens directly (useful for initializing from storage)
|
|
196
204
|
* @param accessToken - JWT access token
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
OxyProvider,
|
|
19
19
|
OxyContextProvider,
|
|
20
20
|
useOxy,
|
|
21
|
+
useAuthFetch,
|
|
21
22
|
|
|
22
23
|
// Components
|
|
23
24
|
OxySignInButton,
|
|
@@ -39,9 +40,8 @@ import { OxyContextState, OxyContextProviderProps } from './ui/context/OxyContex
|
|
|
39
40
|
import * as Models from './models/interfaces';
|
|
40
41
|
|
|
41
42
|
// ------------- Core Exports -------------
|
|
42
|
-
export
|
|
43
|
-
export
|
|
44
|
-
export * from './core';
|
|
43
|
+
export { OxyCore, OxyServices };
|
|
44
|
+
export default OxyServices; // Default export for backward compatibility
|
|
45
45
|
|
|
46
46
|
// ------------- Utility Exports -------------
|
|
47
47
|
export { DeviceManager } from './utils';
|
|
@@ -57,6 +57,7 @@ export {
|
|
|
57
57
|
OxyProvider,
|
|
58
58
|
OxyContextProvider,
|
|
59
59
|
useOxy,
|
|
60
|
+
useAuthFetch,
|
|
60
61
|
|
|
61
62
|
// Components
|
|
62
63
|
OxySignInButton,
|
|
@@ -65,7 +66,6 @@ export {
|
|
|
65
66
|
FollowButton,
|
|
66
67
|
FontLoader,
|
|
67
68
|
OxyIcon,
|
|
68
|
-
useOxyFollow,
|
|
69
69
|
useFollow,
|
|
70
70
|
ProfileScreen,
|
|
71
71
|
OxyRouter,
|
package/src/ui/hooks/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { useOxyFollow, useFollow } from './useOxyFollow';
|
|
1
|
+
export { useOxyFollow, useFollow } from './useOxyFollow';
|
|
2
|
+
export { useAuthFetch } from './useAuthFetch';
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero Config Authenticated Fetch Hook
|
|
3
|
+
*
|
|
4
|
+
* Simple hook that provides fetch-like API with automatic authentication
|
|
5
|
+
* Leverages the existing useOxy hook and OxyProvider infrastructure
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const authFetch = useAuthFetch();
|
|
9
|
+
* const response = await authFetch('/api/protected');
|
|
10
|
+
* const data = await authFetch.get('/api/users');
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useCallback } from 'react';
|
|
14
|
+
import { useOxy } from '../context/OxyContext';
|
|
15
|
+
|
|
16
|
+
export interface AuthFetchOptions extends Omit<RequestInit, 'body'> {
|
|
17
|
+
body?: any; // Allow any type for body, we'll JSON.stringify if needed
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AuthFetchAPI {
|
|
21
|
+
// Main fetch function (drop-in replacement)
|
|
22
|
+
(input: RequestInfo | URL, init?: AuthFetchOptions): Promise<Response>;
|
|
23
|
+
|
|
24
|
+
// Convenience methods for JSON APIs
|
|
25
|
+
get: (endpoint: string, options?: AuthFetchOptions) => Promise<any>;
|
|
26
|
+
post: (endpoint: string, data?: any, options?: AuthFetchOptions) => Promise<any>;
|
|
27
|
+
put: (endpoint: string, data?: any, options?: AuthFetchOptions) => Promise<any>;
|
|
28
|
+
delete: (endpoint: string, options?: AuthFetchOptions) => Promise<any>;
|
|
29
|
+
|
|
30
|
+
// Access to auth state and methods
|
|
31
|
+
isAuthenticated: boolean;
|
|
32
|
+
user: any;
|
|
33
|
+
login: (username: string, password: string) => Promise<any>;
|
|
34
|
+
logout: () => Promise<void>;
|
|
35
|
+
signUp: (username: string, email: string, password: string) => Promise<any>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook that provides authenticated fetch functionality
|
|
40
|
+
* Uses the existing OxyServices instance from useOxy context
|
|
41
|
+
*/
|
|
42
|
+
export function useAuthFetch(): AuthFetchAPI {
|
|
43
|
+
const { oxyServices, isAuthenticated, user, login, logout, signUp, activeSessionId } = useOxy();
|
|
44
|
+
|
|
45
|
+
// Main fetch function with automatic auth headers
|
|
46
|
+
const authFetch = useCallback(async (input: RequestInfo | URL, init?: AuthFetchOptions): Promise<Response> => {
|
|
47
|
+
const url = resolveURL(input, oxyServices.getBaseURL());
|
|
48
|
+
const options = await addAuthHeaders(init, oxyServices, activeSessionId || undefined);
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
let response = await fetch(url, options);
|
|
52
|
+
|
|
53
|
+
// Handle token expiry and automatic refresh
|
|
54
|
+
if (response.status === 401 && oxyServices.getCurrentUserId()) {
|
|
55
|
+
// Try to refresh token and retry
|
|
56
|
+
try {
|
|
57
|
+
await oxyServices.refreshTokens();
|
|
58
|
+
const retryOptions = await addAuthHeaders(init, oxyServices, activeSessionId || undefined);
|
|
59
|
+
response = await fetch(url, retryOptions);
|
|
60
|
+
} catch (refreshError) {
|
|
61
|
+
// Refresh failed, user needs to login again
|
|
62
|
+
console.warn('Token refresh failed, user needs to re-authenticate');
|
|
63
|
+
throw new Error('Authentication expired. Please login again.');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('AuthFetch error:', error);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}, [oxyServices, activeSessionId]);
|
|
73
|
+
|
|
74
|
+
// JSON convenience methods
|
|
75
|
+
const get = useCallback(async (endpoint: string, options?: AuthFetchOptions) => {
|
|
76
|
+
const response = await authFetch(endpoint, { ...options, method: 'GET' });
|
|
77
|
+
return handleJsonResponse(response);
|
|
78
|
+
}, [authFetch]);
|
|
79
|
+
|
|
80
|
+
const post = useCallback(async (endpoint: string, data?: any, options?: AuthFetchOptions) => {
|
|
81
|
+
const response = await authFetch(endpoint, {
|
|
82
|
+
...options,
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
...options?.headers
|
|
87
|
+
},
|
|
88
|
+
body: data ? JSON.stringify(data) : undefined
|
|
89
|
+
});
|
|
90
|
+
return handleJsonResponse(response);
|
|
91
|
+
}, [authFetch]);
|
|
92
|
+
|
|
93
|
+
const put = useCallback(async (endpoint: string, data?: any, options?: AuthFetchOptions) => {
|
|
94
|
+
const response = await authFetch(endpoint, {
|
|
95
|
+
...options,
|
|
96
|
+
method: 'PUT',
|
|
97
|
+
headers: {
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
...options?.headers
|
|
100
|
+
},
|
|
101
|
+
body: data ? JSON.stringify(data) : undefined
|
|
102
|
+
});
|
|
103
|
+
return handleJsonResponse(response);
|
|
104
|
+
}, [authFetch]);
|
|
105
|
+
|
|
106
|
+
const del = useCallback(async (endpoint: string, options?: AuthFetchOptions) => {
|
|
107
|
+
const response = await authFetch(endpoint, { ...options, method: 'DELETE' });
|
|
108
|
+
return handleJsonResponse(response);
|
|
109
|
+
}, [authFetch]);
|
|
110
|
+
|
|
111
|
+
// Attach convenience methods and auth state to the main function
|
|
112
|
+
const fetchWithMethods = authFetch as AuthFetchAPI;
|
|
113
|
+
fetchWithMethods.get = get;
|
|
114
|
+
fetchWithMethods.post = post;
|
|
115
|
+
fetchWithMethods.put = put;
|
|
116
|
+
fetchWithMethods.delete = del;
|
|
117
|
+
fetchWithMethods.isAuthenticated = isAuthenticated;
|
|
118
|
+
fetchWithMethods.user = user;
|
|
119
|
+
fetchWithMethods.login = login;
|
|
120
|
+
fetchWithMethods.logout = logout;
|
|
121
|
+
fetchWithMethods.signUp = signUp;
|
|
122
|
+
|
|
123
|
+
return fetchWithMethods;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Helper functions
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
function resolveURL(input: RequestInfo | URL, baseURL: string): string {
|
|
131
|
+
const url = input.toString();
|
|
132
|
+
|
|
133
|
+
// If it's already a full URL, return as is
|
|
134
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
135
|
+
return url;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If it starts with /, it's relative to base URL
|
|
139
|
+
if (url.startsWith('/')) {
|
|
140
|
+
return `${baseURL}${url}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Otherwise, append to base URL with /
|
|
144
|
+
return `${baseURL}/${url}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function addAuthHeaders(init?: AuthFetchOptions, oxyServices?: any, activeSessionId?: string): Promise<RequestInit> {
|
|
148
|
+
const headers = new Headers(init?.headers);
|
|
149
|
+
|
|
150
|
+
// Add auth header if user is authenticated
|
|
151
|
+
if (oxyServices?.getCurrentUserId() && !headers.has('Authorization')) {
|
|
152
|
+
try {
|
|
153
|
+
// First try to get regular JWT access token
|
|
154
|
+
let accessToken = oxyServices.getAccessToken?.();
|
|
155
|
+
|
|
156
|
+
// If no JWT token but we have a secure session, try to get token from session
|
|
157
|
+
if (!accessToken && activeSessionId) {
|
|
158
|
+
console.log('[Auth API] No JWT token, trying to get token from secure session:', activeSessionId);
|
|
159
|
+
try {
|
|
160
|
+
const tokenData = await oxyServices.getTokenBySession(activeSessionId);
|
|
161
|
+
accessToken = tokenData.accessToken;
|
|
162
|
+
console.log('[Auth API] Got token from session successfully');
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.warn('[Auth API] Failed to get token from session:', error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (accessToken) {
|
|
169
|
+
headers.set('Authorization', `Bearer ${accessToken}`);
|
|
170
|
+
} else {
|
|
171
|
+
console.warn('[Auth API] No authentication token available - JWT token:', !!oxyServices.getAccessToken?.(), 'activeSessionId:', activeSessionId);
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('[Auth API] Error getting access token:', error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const body = init?.body;
|
|
179
|
+
const processedBody = body && typeof body === 'object' && !(body instanceof FormData) && !(body instanceof URLSearchParams)
|
|
180
|
+
? JSON.stringify(body)
|
|
181
|
+
: body;
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
...init,
|
|
185
|
+
headers,
|
|
186
|
+
body: processedBody
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function handleJsonResponse(response: Response): Promise<any> {
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const errorData = await response.json();
|
|
196
|
+
errorMessage = errorData.message || errorData.error || errorMessage;
|
|
197
|
+
} catch {
|
|
198
|
+
// Ignore JSON parsing errors
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const error = new Error(errorMessage) as any;
|
|
202
|
+
error.status = response.status;
|
|
203
|
+
error.response = response;
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
return await response.json();
|
|
209
|
+
} catch {
|
|
210
|
+
// If response isn't JSON, return the response itself
|
|
211
|
+
return response;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export default useAuthFetch;
|
package/src/ui/index.ts
CHANGED