@oxyhq/auth 1.1.3 → 1.1.4
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/dist/cjs/WebOxyProvider.js +20 -0
- package/dist/cjs/hooks/mutations/useAccountMutations.js +5 -5
- package/dist/cjs/hooks/queries/useAccountQueries.js +2 -2
- package/dist/cjs/hooks/queries/useServicesQueries.js +2 -2
- package/dist/cjs/index.js +2 -17
- package/dist/cjs/utils/avatarUtils.js +2 -2
- package/dist/esm/WebOxyProvider.js +21 -1
- package/dist/esm/hooks/mutations/useAccountMutations.js +1 -1
- package/dist/esm/hooks/queries/useAccountQueries.js +1 -1
- package/dist/esm/hooks/queries/useServicesQueries.js +1 -1
- package/dist/esm/index.js +0 -4
- package/dist/esm/utils/avatarUtils.js +1 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/utils/avatarUtils.d.ts +1 -2
- package/package.json +1 -1
- package/src/WebOxyProvider.tsx +16 -0
- package/src/hooks/mutations/useAccountMutations.ts +1 -1
- package/src/hooks/queries/useAccountQueries.ts +1 -1
- package/src/hooks/queries/useServicesQueries.ts +1 -1
- package/src/index.ts +0 -35
- package/src/utils/avatarUtils.ts +2 -3
- package/src/utils/authHelpers.ts +0 -183
|
@@ -49,6 +49,8 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
|
|
|
49
49
|
// Multi-session management is handled by @oxyhq/services (OxyContext) for RN apps.
|
|
50
50
|
const sessions = [];
|
|
51
51
|
const isAuthenticated = !!user;
|
|
52
|
+
// Mutex: prevents concurrent sign-in attempts (FedCM + popup + redirect)
|
|
53
|
+
const signingInRef = (0, react_1.useRef)(false);
|
|
52
54
|
const handleAuthSuccess = (0, react_1.useCallback)(async (session, method = 'credentials') => {
|
|
53
55
|
await authManager.handleAuthSuccess(session, method);
|
|
54
56
|
if (session.sessionId) {
|
|
@@ -127,6 +129,9 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
|
|
|
127
129
|
onAuthStateChange?.(user);
|
|
128
130
|
}, [user, onAuthStateChange]);
|
|
129
131
|
const signIn = (0, react_1.useCallback)(async () => {
|
|
132
|
+
if (signingInRef.current)
|
|
133
|
+
return;
|
|
134
|
+
signingInRef.current = true;
|
|
130
135
|
setError(null);
|
|
131
136
|
setIsLoading(true);
|
|
132
137
|
let selectedMethod = 'popup';
|
|
@@ -147,8 +152,14 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
|
|
|
147
152
|
catch (err) {
|
|
148
153
|
handleAuthError(err);
|
|
149
154
|
}
|
|
155
|
+
finally {
|
|
156
|
+
signingInRef.current = false;
|
|
157
|
+
}
|
|
150
158
|
}, [crossDomainAuth, preferredAuthMethod, handleAuthSuccess, handleAuthError]);
|
|
151
159
|
const signInWithFedCM = (0, react_1.useCallback)(async () => {
|
|
160
|
+
if (signingInRef.current)
|
|
161
|
+
return;
|
|
162
|
+
signingInRef.current = true;
|
|
152
163
|
setError(null);
|
|
153
164
|
setIsLoading(true);
|
|
154
165
|
try {
|
|
@@ -158,8 +169,14 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
|
|
|
158
169
|
catch (err) {
|
|
159
170
|
handleAuthError(err);
|
|
160
171
|
}
|
|
172
|
+
finally {
|
|
173
|
+
signingInRef.current = false;
|
|
174
|
+
}
|
|
161
175
|
}, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
|
|
162
176
|
const signInWithPopup = (0, react_1.useCallback)(async () => {
|
|
177
|
+
if (signingInRef.current)
|
|
178
|
+
return;
|
|
179
|
+
signingInRef.current = true;
|
|
163
180
|
setError(null);
|
|
164
181
|
setIsLoading(true);
|
|
165
182
|
try {
|
|
@@ -169,6 +186,9 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
|
|
|
169
186
|
catch (err) {
|
|
170
187
|
handleAuthError(err);
|
|
171
188
|
}
|
|
189
|
+
finally {
|
|
190
|
+
signingInRef.current = false;
|
|
191
|
+
}
|
|
172
192
|
}, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
|
|
173
193
|
const signInWithRedirect = (0, react_1.useCallback)(() => {
|
|
174
194
|
setError(null);
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useUploadFile = exports.useUpdatePrivacySettings = exports.useUpdateAccountSettings = exports.useUploadAvatar = exports.useUpdateProfile = void 0;
|
|
4
4
|
const react_query_1 = require("@tanstack/react-query");
|
|
5
|
+
const core_1 = require("@oxyhq/core");
|
|
5
6
|
const queryKeys_1 = require("../queries/queryKeys");
|
|
6
7
|
const WebOxyProvider_1 = require("../../WebOxyProvider");
|
|
7
8
|
const sonner_1 = require("sonner");
|
|
8
9
|
const avatarUtils_1 = require("../../utils/avatarUtils");
|
|
9
10
|
const authStore_1 = require("../../stores/authStore");
|
|
10
|
-
const authHelpers_1 = require("../../utils/authHelpers");
|
|
11
11
|
/**
|
|
12
12
|
* Update user profile with optimistic updates and offline queue support
|
|
13
13
|
*/
|
|
@@ -16,7 +16,7 @@ const useUpdateProfile = () => {
|
|
|
16
16
|
const queryClient = (0, react_query_1.useQueryClient)();
|
|
17
17
|
return (0, react_query_1.useMutation)({
|
|
18
18
|
mutationFn: async (updates) => {
|
|
19
|
-
return (0,
|
|
19
|
+
return (0, core_1.authenticatedApiCall)(oxyServices, activeSessionId, () => oxyServices.updateProfile(updates));
|
|
20
20
|
},
|
|
21
21
|
// Optimistic update
|
|
22
22
|
onMutate: async (updates) => {
|
|
@@ -78,7 +78,7 @@ const useUploadAvatar = () => {
|
|
|
78
78
|
const queryClient = (0, react_query_1.useQueryClient)();
|
|
79
79
|
return (0, react_query_1.useMutation)({
|
|
80
80
|
mutationFn: async (file) => {
|
|
81
|
-
return (0,
|
|
81
|
+
return (0, core_1.authenticatedApiCall)(oxyServices, activeSessionId, async () => {
|
|
82
82
|
// Upload file first
|
|
83
83
|
const uploadResult = await oxyServices.assetUpload(file, 'public');
|
|
84
84
|
const fileId = uploadResult?.file?.id || uploadResult?.id || uploadResult;
|
|
@@ -188,7 +188,7 @@ const useUpdatePrivacySettings = () => {
|
|
|
188
188
|
if (!targetUserId) {
|
|
189
189
|
throw new Error('User ID is required');
|
|
190
190
|
}
|
|
191
|
-
return (0,
|
|
191
|
+
return (0, core_1.authenticatedApiCall)(oxyServices, activeSessionId, () => oxyServices.updatePrivacySettings(settings, targetUserId));
|
|
192
192
|
},
|
|
193
193
|
// Optimistic update
|
|
194
194
|
onMutate: async ({ settings, userId }) => {
|
|
@@ -268,7 +268,7 @@ const useUploadFile = () => {
|
|
|
268
268
|
const { oxyServices, activeSessionId } = (0, WebOxyProvider_1.useWebOxy)();
|
|
269
269
|
return (0, react_query_1.useMutation)({
|
|
270
270
|
mutationFn: async ({ file, visibility, metadata, onProgress }) => {
|
|
271
|
-
return (0,
|
|
271
|
+
return (0, core_1.authenticatedApiCall)(oxyServices, activeSessionId, () => oxyServices.assetUpload(file, visibility, metadata, onProgress));
|
|
272
272
|
},
|
|
273
273
|
});
|
|
274
274
|
};
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.usePrivacySettings = exports.useUsersBySessions = exports.useUserByUsername = exports.useUserById = exports.useCurrentUser = exports.useUserProfiles = exports.useUserProfile = void 0;
|
|
4
4
|
const react_query_1 = require("@tanstack/react-query");
|
|
5
|
+
const core_1 = require("@oxyhq/core");
|
|
5
6
|
const queryKeys_1 = require("./queryKeys");
|
|
6
7
|
const WebOxyProvider_1 = require("../../WebOxyProvider");
|
|
7
|
-
const authHelpers_1 = require("../../utils/authHelpers");
|
|
8
8
|
/**
|
|
9
9
|
* Get user profile by session ID
|
|
10
10
|
*/
|
|
@@ -131,7 +131,7 @@ const usePrivacySettings = (userId, options) => {
|
|
|
131
131
|
if (!targetUserId) {
|
|
132
132
|
throw new Error('User ID is required');
|
|
133
133
|
}
|
|
134
|
-
return (0,
|
|
134
|
+
return (0, core_1.authenticatedApiCall)(oxyServices, activeSessionId, () => oxyServices.getPrivacySettings(targetUserId));
|
|
135
135
|
},
|
|
136
136
|
enabled: (options?.enabled !== false) && !!targetUserId,
|
|
137
137
|
staleTime: 2 * 60 * 1000, // 2 minutes
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useSecurityInfo = exports.useUserDevices = exports.useDeviceSessions = exports.useSession = exports.useSessions = void 0;
|
|
4
4
|
const react_query_1 = require("@tanstack/react-query");
|
|
5
|
+
const core_1 = require("@oxyhq/core");
|
|
5
6
|
const queryKeys_1 = require("./queryKeys");
|
|
6
7
|
const WebOxyProvider_1 = require("../../WebOxyProvider");
|
|
7
8
|
const sessionHelpers_1 = require("../../utils/sessionHelpers");
|
|
8
|
-
const authHelpers_1 = require("../../utils/authHelpers");
|
|
9
9
|
/**
|
|
10
10
|
* Get all active sessions for the current user
|
|
11
11
|
*/
|
|
@@ -87,7 +87,7 @@ const useUserDevices = (options) => {
|
|
|
87
87
|
return (0, react_query_1.useQuery)({
|
|
88
88
|
queryKey: queryKeys_1.queryKeys.devices.list(),
|
|
89
89
|
queryFn: async () => {
|
|
90
|
-
return (0,
|
|
90
|
+
return (0, core_1.authenticatedApiCall)(oxyServices, activeSessionId, () => oxyServices.getUserDevices());
|
|
91
91
|
},
|
|
92
92
|
enabled: (options?.enabled !== false) && isAuthenticated,
|
|
93
93
|
staleTime: 5 * 60 * 1000,
|
package/dist/cjs/index.js
CHANGED
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
exports.
|
|
28
|
-
exports.
|
|
27
|
+
exports.isInvalidSessionError = exports.handleAuthError = exports.useFileFiltering = exports.useFollowerCounts = exports.useFollow = exports.setOxyFileUrlInstance = exports.useFileDownloadUrl = exports.setOxyAssetInstance = exports.useAssets = exports.useSessionSocket = exports.createGenericMutation = exports.createProfileMutation = exports.useRemoveDevice = exports.useUpdateDeviceName = exports.useLogoutAll = exports.useLogoutSession = exports.useSwitchSession = exports.useUploadFile = exports.useUpdatePrivacySettings = exports.useUpdateAccountSettings = exports.useUploadAvatar = exports.useUpdateProfile = exports.useRecentSecurityActivity = exports.useSecurityActivity = exports.useSecurityInfo = exports.useUserDevices = exports.useDeviceSessions = exports.useSession = exports.useSessions = exports.usePrivacySettings = exports.useUsersBySessions = exports.useUserByUsername = exports.useUserById = exports.useCurrentUser = exports.useUserProfiles = exports.useUserProfile = exports.useIsAssetLinked = exports.useAssetUsageCount = exports.useAssetsByEntity = exports.useAssetsByApp = exports.useAssetErrors = exports.useAssetLoading = exports.useUploadProgress = exports.useAsset = exports.useAssetsStore = exports.useAssetStore = exports.useAuthStore = exports.useAuth = exports.useWebOxy = exports.WebOxyProvider = void 0;
|
|
28
|
+
exports.extractErrorMessage = exports.isTimeoutOrNetworkError = void 0;
|
|
29
29
|
// --- Provider & Hooks ---
|
|
30
30
|
var WebOxyProvider_1 = require("./WebOxyProvider");
|
|
31
31
|
Object.defineProperty(exports, "WebOxyProvider", { enumerable: true, get: function () { return WebOxyProvider_1.WebOxyProvider; } });
|
|
@@ -90,26 +90,11 @@ Object.defineProperty(exports, "useFollow", { enumerable: true, get: function ()
|
|
|
90
90
|
Object.defineProperty(exports, "useFollowerCounts", { enumerable: true, get: function () { return useFollow_1.useFollowerCounts; } });
|
|
91
91
|
var useFileFiltering_1 = require("./hooks/useFileFiltering");
|
|
92
92
|
Object.defineProperty(exports, "useFileFiltering", { enumerable: true, get: function () { return useFileFiltering_1.useFileFiltering; } });
|
|
93
|
-
// --- Auth Helpers ---
|
|
94
|
-
var authHelpers_1 = require("./utils/authHelpers");
|
|
95
|
-
Object.defineProperty(exports, "ensureValidToken", { enumerable: true, get: function () { return authHelpers_1.ensureValidToken; } });
|
|
96
|
-
Object.defineProperty(exports, "withAuthErrorHandling", { enumerable: true, get: function () { return authHelpers_1.withAuthErrorHandling; } });
|
|
97
|
-
Object.defineProperty(exports, "authenticatedApiCall", { enumerable: true, get: function () { return authHelpers_1.authenticatedApiCall; } });
|
|
98
|
-
Object.defineProperty(exports, "isAuthenticationError", { enumerable: true, get: function () { return authHelpers_1.isAuthenticationError; } });
|
|
99
|
-
Object.defineProperty(exports, "SessionSyncRequiredError", { enumerable: true, get: function () { return authHelpers_1.SessionSyncRequiredError; } });
|
|
100
|
-
Object.defineProperty(exports, "AuthenticationFailedError", { enumerable: true, get: function () { return authHelpers_1.AuthenticationFailedError; } });
|
|
101
93
|
// --- Error Handlers ---
|
|
102
94
|
var errorHandlers_1 = require("./utils/errorHandlers");
|
|
103
95
|
Object.defineProperty(exports, "handleAuthError", { enumerable: true, get: function () { return errorHandlers_1.handleAuthError; } });
|
|
104
96
|
Object.defineProperty(exports, "isInvalidSessionError", { enumerable: true, get: function () { return errorHandlers_1.isInvalidSessionError; } });
|
|
105
97
|
Object.defineProperty(exports, "isTimeoutOrNetworkError", { enumerable: true, get: function () { return errorHandlers_1.isTimeoutOrNetworkError; } });
|
|
106
98
|
Object.defineProperty(exports, "extractErrorMessage", { enumerable: true, get: function () { return errorHandlers_1.extractErrorMessage; } });
|
|
107
|
-
// Re-export core for convenience
|
|
108
|
-
var core_1 = require("@oxyhq/core");
|
|
109
|
-
Object.defineProperty(exports, "OxyServices", { enumerable: true, get: function () { return core_1.OxyServices; } });
|
|
110
|
-
Object.defineProperty(exports, "CrossDomainAuth", { enumerable: true, get: function () { return core_1.CrossDomainAuth; } });
|
|
111
|
-
Object.defineProperty(exports, "AuthManager", { enumerable: true, get: function () { return core_1.AuthManager; } });
|
|
112
|
-
Object.defineProperty(exports, "createAuthManager", { enumerable: true, get: function () { return core_1.createAuthManager; } });
|
|
113
|
-
Object.defineProperty(exports, "createCrossDomainAuth", { enumerable: true, get: function () { return core_1.createCrossDomainAuth; } });
|
|
114
99
|
const WebOxyProvider_2 = require("./WebOxyProvider");
|
|
115
100
|
exports.default = WebOxyProvider_2.WebOxyProvider;
|
|
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.updateAvatarVisibility = updateAvatarVisibility;
|
|
4
4
|
exports.refreshAvatarInStore = refreshAvatarInStore;
|
|
5
5
|
exports.updateProfileWithAvatar = updateProfileWithAvatar;
|
|
6
|
+
const core_1 = require("@oxyhq/core");
|
|
6
7
|
const accountStore_1 = require("../stores/accountStore");
|
|
7
8
|
const authStore_1 = require("../stores/authStore");
|
|
8
9
|
const queryKeys_1 = require("../hooks/queries/queryKeys");
|
|
9
|
-
const authHelpers_1 = require("./authHelpers");
|
|
10
10
|
/**
|
|
11
11
|
* Updates file visibility to public for avatar use.
|
|
12
12
|
* Handles errors gracefully, only logging non-404 errors.
|
|
@@ -58,7 +58,7 @@ function refreshAvatarInStore(sessionId, avatarFileId, oxyServices) {
|
|
|
58
58
|
* @returns Promise that resolves with updated user data
|
|
59
59
|
*/
|
|
60
60
|
async function updateProfileWithAvatar(updates, oxyServices, activeSessionId, queryClient, syncSession) {
|
|
61
|
-
const data = await (0,
|
|
61
|
+
const data = await (0, core_1.authenticatedApiCall)(oxyServices, activeSessionId, () => oxyServices.updateProfile(updates), syncSession);
|
|
62
62
|
// Update cache with server response
|
|
63
63
|
queryClient.setQueryData(queryKeys_1.queryKeys.accounts.current(), data);
|
|
64
64
|
if (activeSessionId) {
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
* Provides FedCM, popup, and redirect authentication methods.
|
|
7
7
|
* Uses centralized AuthManager for token and session management.
|
|
8
8
|
*/
|
|
9
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
9
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
10
10
|
import { OxyServices, CrossDomainAuth, createAuthManager, } from '@oxyhq/core';
|
|
11
11
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
12
12
|
import { createQueryClient } from './hooks/queryClient';
|
|
@@ -44,6 +44,8 @@ export function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChang
|
|
|
44
44
|
// Multi-session management is handled by @oxyhq/services (OxyContext) for RN apps.
|
|
45
45
|
const sessions = [];
|
|
46
46
|
const isAuthenticated = !!user;
|
|
47
|
+
// Mutex: prevents concurrent sign-in attempts (FedCM + popup + redirect)
|
|
48
|
+
const signingInRef = useRef(false);
|
|
47
49
|
const handleAuthSuccess = useCallback(async (session, method = 'credentials') => {
|
|
48
50
|
await authManager.handleAuthSuccess(session, method);
|
|
49
51
|
if (session.sessionId) {
|
|
@@ -122,6 +124,9 @@ export function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChang
|
|
|
122
124
|
onAuthStateChange?.(user);
|
|
123
125
|
}, [user, onAuthStateChange]);
|
|
124
126
|
const signIn = useCallback(async () => {
|
|
127
|
+
if (signingInRef.current)
|
|
128
|
+
return;
|
|
129
|
+
signingInRef.current = true;
|
|
125
130
|
setError(null);
|
|
126
131
|
setIsLoading(true);
|
|
127
132
|
let selectedMethod = 'popup';
|
|
@@ -142,8 +147,14 @@ export function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChang
|
|
|
142
147
|
catch (err) {
|
|
143
148
|
handleAuthError(err);
|
|
144
149
|
}
|
|
150
|
+
finally {
|
|
151
|
+
signingInRef.current = false;
|
|
152
|
+
}
|
|
145
153
|
}, [crossDomainAuth, preferredAuthMethod, handleAuthSuccess, handleAuthError]);
|
|
146
154
|
const signInWithFedCM = useCallback(async () => {
|
|
155
|
+
if (signingInRef.current)
|
|
156
|
+
return;
|
|
157
|
+
signingInRef.current = true;
|
|
147
158
|
setError(null);
|
|
148
159
|
setIsLoading(true);
|
|
149
160
|
try {
|
|
@@ -153,8 +164,14 @@ export function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChang
|
|
|
153
164
|
catch (err) {
|
|
154
165
|
handleAuthError(err);
|
|
155
166
|
}
|
|
167
|
+
finally {
|
|
168
|
+
signingInRef.current = false;
|
|
169
|
+
}
|
|
156
170
|
}, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
|
|
157
171
|
const signInWithPopup = useCallback(async () => {
|
|
172
|
+
if (signingInRef.current)
|
|
173
|
+
return;
|
|
174
|
+
signingInRef.current = true;
|
|
158
175
|
setError(null);
|
|
159
176
|
setIsLoading(true);
|
|
160
177
|
try {
|
|
@@ -164,6 +181,9 @@ export function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChang
|
|
|
164
181
|
catch (err) {
|
|
165
182
|
handleAuthError(err);
|
|
166
183
|
}
|
|
184
|
+
finally {
|
|
185
|
+
signingInRef.current = false;
|
|
186
|
+
}
|
|
167
187
|
}, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
|
|
168
188
|
const signInWithRedirect = useCallback(() => {
|
|
169
189
|
setError(null);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
2
3
|
import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from '../queries/queryKeys';
|
|
3
4
|
import { useWebOxy } from '../../WebOxyProvider';
|
|
4
5
|
import { toast } from 'sonner';
|
|
5
6
|
import { refreshAvatarInStore } from '../../utils/avatarUtils';
|
|
6
7
|
import { useAuthStore } from '../../stores/authStore';
|
|
7
|
-
import { authenticatedApiCall } from '../../utils/authHelpers';
|
|
8
8
|
/**
|
|
9
9
|
* Update user profile with optimistic updates and offline queue support
|
|
10
10
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useQuery, useQueries } from '@tanstack/react-query';
|
|
2
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
2
3
|
import { queryKeys } from './queryKeys';
|
|
3
4
|
import { useWebOxy } from '../../WebOxyProvider';
|
|
4
|
-
import { authenticatedApiCall } from '../../utils/authHelpers';
|
|
5
5
|
/**
|
|
6
6
|
* Get user profile by session ID
|
|
7
7
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
2
3
|
import { queryKeys } from './queryKeys';
|
|
3
4
|
import { useWebOxy } from '../../WebOxyProvider';
|
|
4
5
|
import { fetchSessionsWithFallback, mapSessionsToClient } from '../../utils/sessionHelpers';
|
|
5
|
-
import { authenticatedApiCall } from '../../utils/authHelpers';
|
|
6
6
|
/**
|
|
7
7
|
* Get all active sessions for the current user
|
|
8
8
|
*/
|
package/dist/esm/index.js
CHANGED
|
@@ -38,11 +38,7 @@ export { useAssets, setOxyAssetInstance } from './hooks/useAssets';
|
|
|
38
38
|
export { useFileDownloadUrl, setOxyFileUrlInstance } from './hooks/useFileDownloadUrl';
|
|
39
39
|
export { useFollow, useFollowerCounts } from './hooks/useFollow';
|
|
40
40
|
export { useFileFiltering } from './hooks/useFileFiltering';
|
|
41
|
-
// --- Auth Helpers ---
|
|
42
|
-
export { ensureValidToken, withAuthErrorHandling, authenticatedApiCall, isAuthenticationError, SessionSyncRequiredError, AuthenticationFailedError, } from './utils/authHelpers';
|
|
43
41
|
// --- Error Handlers ---
|
|
44
42
|
export { handleAuthError, isInvalidSessionError, isTimeoutOrNetworkError, extractErrorMessage, } from './utils/errorHandlers';
|
|
45
|
-
// Re-export core for convenience
|
|
46
|
-
export { OxyServices, CrossDomainAuth, AuthManager, createAuthManager, createCrossDomainAuth, } from '@oxyhq/core';
|
|
47
43
|
import { WebOxyProvider as _WebOxyProvider } from './WebOxyProvider';
|
|
48
44
|
export default _WebOxyProvider;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
1
2
|
import { useAccountStore } from '../stores/accountStore';
|
|
2
3
|
import { useAuthStore } from '../stores/authStore';
|
|
3
4
|
import { queryKeys, invalidateUserQueries, invalidateAccountQueries } from '../hooks/queries/queryKeys';
|
|
4
|
-
import { authenticatedApiCall } from './authHelpers';
|
|
5
5
|
/**
|
|
6
6
|
* Updates file visibility to public for avatar use.
|
|
7
7
|
* Handles errors gracefully, only logging non-404 errors.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -36,11 +36,7 @@ export { useFileDownloadUrl, setOxyFileUrlInstance } from './hooks/useFileDownlo
|
|
|
36
36
|
export { useFollow, useFollowerCounts } from './hooks/useFollow';
|
|
37
37
|
export { useFileFiltering } from './hooks/useFileFiltering';
|
|
38
38
|
export type { ViewMode, SortBy, SortOrder } from './hooks/useFileFiltering';
|
|
39
|
-
export { ensureValidToken, withAuthErrorHandling, authenticatedApiCall, isAuthenticationError, SessionSyncRequiredError, AuthenticationFailedError, } from './utils/authHelpers';
|
|
40
|
-
export type { HandleApiErrorOptions } from './utils/authHelpers';
|
|
41
39
|
export { handleAuthError, isInvalidSessionError, isTimeoutOrNetworkError, extractErrorMessage, } from './utils/errorHandlers';
|
|
42
40
|
export type { HandleAuthErrorOptions } from './utils/errorHandlers';
|
|
43
|
-
export { OxyServices, CrossDomainAuth, AuthManager, createAuthManager, createCrossDomainAuth, } from '@oxyhq/core';
|
|
44
|
-
export type { User, LoginResponse, ApiError, SessionLoginResponse, ClientSession, MinimalUserData, OxyConfig, StorageAdapter, AuthStateChangeCallback, AuthMethod, AuthManagerConfig, CrossDomainAuthOptions, } from '@oxyhq/core';
|
|
45
41
|
import { WebOxyProvider as _WebOxyProvider } from './WebOxyProvider';
|
|
46
42
|
export default _WebOxyProvider;
|
package/package.json
CHANGED
package/src/WebOxyProvider.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
useContext,
|
|
13
13
|
useEffect,
|
|
14
14
|
useMemo,
|
|
15
|
+
useRef,
|
|
15
16
|
useState,
|
|
16
17
|
type ReactNode,
|
|
17
18
|
} from 'react';
|
|
@@ -112,6 +113,9 @@ export function WebOxyProvider({
|
|
|
112
113
|
|
|
113
114
|
const isAuthenticated = !!user;
|
|
114
115
|
|
|
116
|
+
// Mutex: prevents concurrent sign-in attempts (FedCM + popup + redirect)
|
|
117
|
+
const signingInRef = useRef(false);
|
|
118
|
+
|
|
115
119
|
const handleAuthSuccess = useCallback(async (
|
|
116
120
|
session: SessionLoginResponse,
|
|
117
121
|
method: 'fedcm' | 'popup' | 'redirect' | 'credentials' = 'credentials'
|
|
@@ -201,6 +205,8 @@ export function WebOxyProvider({
|
|
|
201
205
|
}, [user, onAuthStateChange]);
|
|
202
206
|
|
|
203
207
|
const signIn = useCallback(async () => {
|
|
208
|
+
if (signingInRef.current) return;
|
|
209
|
+
signingInRef.current = true;
|
|
204
210
|
setError(null);
|
|
205
211
|
setIsLoading(true);
|
|
206
212
|
|
|
@@ -221,10 +227,14 @@ export function WebOxyProvider({
|
|
|
221
227
|
}
|
|
222
228
|
} catch (err) {
|
|
223
229
|
handleAuthError(err);
|
|
230
|
+
} finally {
|
|
231
|
+
signingInRef.current = false;
|
|
224
232
|
}
|
|
225
233
|
}, [crossDomainAuth, preferredAuthMethod, handleAuthSuccess, handleAuthError]);
|
|
226
234
|
|
|
227
235
|
const signInWithFedCM = useCallback(async () => {
|
|
236
|
+
if (signingInRef.current) return;
|
|
237
|
+
signingInRef.current = true;
|
|
228
238
|
setError(null);
|
|
229
239
|
setIsLoading(true);
|
|
230
240
|
try {
|
|
@@ -232,10 +242,14 @@ export function WebOxyProvider({
|
|
|
232
242
|
await handleAuthSuccess(session, 'fedcm');
|
|
233
243
|
} catch (err) {
|
|
234
244
|
handleAuthError(err);
|
|
245
|
+
} finally {
|
|
246
|
+
signingInRef.current = false;
|
|
235
247
|
}
|
|
236
248
|
}, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
|
|
237
249
|
|
|
238
250
|
const signInWithPopup = useCallback(async () => {
|
|
251
|
+
if (signingInRef.current) return;
|
|
252
|
+
signingInRef.current = true;
|
|
239
253
|
setError(null);
|
|
240
254
|
setIsLoading(true);
|
|
241
255
|
try {
|
|
@@ -243,6 +257,8 @@ export function WebOxyProvider({
|
|
|
243
257
|
await handleAuthSuccess(session, 'popup');
|
|
244
258
|
} catch (err) {
|
|
245
259
|
handleAuthError(err);
|
|
260
|
+
} finally {
|
|
261
|
+
signingInRef.current = false;
|
|
246
262
|
}
|
|
247
263
|
}, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
|
|
248
264
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
2
3
|
import type { User } from '@oxyhq/core';
|
|
3
4
|
import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from '../queries/queryKeys';
|
|
4
5
|
import { useWebOxy } from '../../WebOxyProvider';
|
|
5
6
|
import { toast } from 'sonner';
|
|
6
7
|
import { refreshAvatarInStore } from '../../utils/avatarUtils';
|
|
7
8
|
import { useAuthStore } from '../../stores/authStore';
|
|
8
|
-
import { authenticatedApiCall } from '../../utils/authHelpers';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Update user profile with optimistic updates and offline queue support
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useQuery, useQueries } from '@tanstack/react-query';
|
|
2
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
2
3
|
import type { User } from '@oxyhq/core';
|
|
3
4
|
import { queryKeys } from './queryKeys';
|
|
4
5
|
import { useWebOxy } from '../../WebOxyProvider';
|
|
5
|
-
import { authenticatedApiCall } from '../../utils/authHelpers';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Get user profile by session ID
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
2
3
|
import type { ClientSession } from '@oxyhq/core';
|
|
3
4
|
import { queryKeys } from './queryKeys';
|
|
4
5
|
import { useWebOxy } from '../../WebOxyProvider';
|
|
5
6
|
import { fetchSessionsWithFallback, mapSessionsToClient } from '../../utils/sessionHelpers';
|
|
6
|
-
import { authenticatedApiCall } from '../../utils/authHelpers';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Get all active sessions for the current user
|
package/src/index.ts
CHANGED
|
@@ -96,17 +96,6 @@ export { useFollow, useFollowerCounts } from './hooks/useFollow';
|
|
|
96
96
|
export { useFileFiltering } from './hooks/useFileFiltering';
|
|
97
97
|
export type { ViewMode, SortBy, SortOrder } from './hooks/useFileFiltering';
|
|
98
98
|
|
|
99
|
-
// --- Auth Helpers ---
|
|
100
|
-
export {
|
|
101
|
-
ensureValidToken,
|
|
102
|
-
withAuthErrorHandling,
|
|
103
|
-
authenticatedApiCall,
|
|
104
|
-
isAuthenticationError,
|
|
105
|
-
SessionSyncRequiredError,
|
|
106
|
-
AuthenticationFailedError,
|
|
107
|
-
} from './utils/authHelpers';
|
|
108
|
-
export type { HandleApiErrorOptions } from './utils/authHelpers';
|
|
109
|
-
|
|
110
99
|
// --- Error Handlers ---
|
|
111
100
|
export {
|
|
112
101
|
handleAuthError,
|
|
@@ -116,29 +105,5 @@ export {
|
|
|
116
105
|
} from './utils/errorHandlers';
|
|
117
106
|
export type { HandleAuthErrorOptions } from './utils/errorHandlers';
|
|
118
107
|
|
|
119
|
-
// Re-export core for convenience
|
|
120
|
-
export {
|
|
121
|
-
OxyServices,
|
|
122
|
-
CrossDomainAuth,
|
|
123
|
-
AuthManager,
|
|
124
|
-
createAuthManager,
|
|
125
|
-
createCrossDomainAuth,
|
|
126
|
-
} from '@oxyhq/core';
|
|
127
|
-
|
|
128
|
-
export type {
|
|
129
|
-
User,
|
|
130
|
-
LoginResponse,
|
|
131
|
-
ApiError,
|
|
132
|
-
SessionLoginResponse,
|
|
133
|
-
ClientSession,
|
|
134
|
-
MinimalUserData,
|
|
135
|
-
OxyConfig,
|
|
136
|
-
StorageAdapter,
|
|
137
|
-
AuthStateChangeCallback,
|
|
138
|
-
AuthMethod,
|
|
139
|
-
AuthManagerConfig,
|
|
140
|
-
CrossDomainAuthOptions,
|
|
141
|
-
} from '@oxyhq/core';
|
|
142
|
-
|
|
143
108
|
import { WebOxyProvider as _WebOxyProvider } from './WebOxyProvider';
|
|
144
109
|
export default _WebOxyProvider;
|
package/src/utils/avatarUtils.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { User } from '@oxyhq/core';
|
|
1
|
+
import { authenticatedApiCall } from '@oxyhq/core';
|
|
2
|
+
import type { OxyServices, User } from '@oxyhq/core';
|
|
3
3
|
import { useAccountStore } from '../stores/accountStore';
|
|
4
4
|
import { useAuthStore } from '../stores/authStore';
|
|
5
5
|
import { QueryClient } from '@tanstack/react-query';
|
|
6
6
|
import { queryKeys, invalidateUserQueries, invalidateAccountQueries } from '../hooks/queries/queryKeys';
|
|
7
|
-
import { authenticatedApiCall } from './authHelpers';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Updates file visibility to public for avatar use.
|
package/src/utils/authHelpers.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authentication helper utilities to reduce code duplication across hooks and utilities.
|
|
3
|
-
* These functions handle common token validation and authentication error patterns.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { OxyServices } from '@oxyhq/core';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Error thrown when session sync is required
|
|
10
|
-
*/
|
|
11
|
-
export class SessionSyncRequiredError extends Error {
|
|
12
|
-
constructor(message = 'Session needs to be synced. Please try again.') {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = 'SessionSyncRequiredError';
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Error thrown when authentication fails
|
|
20
|
-
*/
|
|
21
|
-
export class AuthenticationFailedError extends Error {
|
|
22
|
-
constructor(message = 'Authentication failed. Please sign in again.') {
|
|
23
|
-
super(message);
|
|
24
|
-
this.name = 'AuthenticationFailedError';
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Ensures a valid token exists before making authenticated API calls.
|
|
30
|
-
* If no valid token exists and an active session ID is available,
|
|
31
|
-
* attempts to refresh the token using the session.
|
|
32
|
-
*
|
|
33
|
-
* @param oxyServices - The OxyServices instance
|
|
34
|
-
* @param activeSessionId - The active session ID (if available)
|
|
35
|
-
* @throws {SessionSyncRequiredError} If the session needs to be synced (offline session)
|
|
36
|
-
* @throws {Error} If token refresh fails for other reasons
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```ts
|
|
40
|
-
* // In a mutation or query function:
|
|
41
|
-
* await ensureValidToken(oxyServices, activeSessionId);
|
|
42
|
-
* return await oxyServices.updateProfile(updates);
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
export async function ensureValidToken(
|
|
46
|
-
oxyServices: OxyServices,
|
|
47
|
-
activeSessionId: string | null | undefined
|
|
48
|
-
): Promise<void> {
|
|
49
|
-
if (oxyServices.hasValidToken() || !activeSessionId) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
await oxyServices.getTokenBySession(activeSessionId);
|
|
55
|
-
} catch (tokenError) {
|
|
56
|
-
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
57
|
-
|
|
58
|
-
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
59
|
-
throw new SessionSyncRequiredError();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
throw tokenError;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Options for handling API authentication errors
|
|
68
|
-
*/
|
|
69
|
-
export interface HandleApiErrorOptions {
|
|
70
|
-
/** Optional callback to attempt session sync and retry */
|
|
71
|
-
syncSession?: () => Promise<unknown>;
|
|
72
|
-
/** The active session ID for retry attempts */
|
|
73
|
-
activeSessionId?: string | null;
|
|
74
|
-
/** The OxyServices instance for retry attempts */
|
|
75
|
-
oxyServices?: OxyServices;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Checks if an error is an authentication error (401 or auth-related message)
|
|
80
|
-
*
|
|
81
|
-
* @param error - The error to check
|
|
82
|
-
* @returns True if the error is an authentication error
|
|
83
|
-
*/
|
|
84
|
-
export function isAuthenticationError(error: unknown): boolean {
|
|
85
|
-
if (!error || typeof error !== 'object') {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const errorObj = error as { message?: string; status?: number; response?: { status?: number } };
|
|
90
|
-
const errorMessage = errorObj.message || '';
|
|
91
|
-
const status = errorObj.status || errorObj.response?.status;
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
status === 401 ||
|
|
95
|
-
errorMessage.includes('Authentication required') ||
|
|
96
|
-
errorMessage.includes('Invalid or missing authorization header')
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Wraps an API call with authentication error handling.
|
|
102
|
-
* If an authentication error occurs, it can optionally attempt to sync the session and retry.
|
|
103
|
-
*
|
|
104
|
-
* @param apiCall - The API call function to execute
|
|
105
|
-
* @param options - Optional error handling configuration
|
|
106
|
-
* @returns The result of the API call
|
|
107
|
-
* @throws {AuthenticationFailedError} If authentication fails and cannot be recovered
|
|
108
|
-
* @throws {Error} If the API call fails for non-auth reasons
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* ```ts
|
|
112
|
-
* // Simple usage:
|
|
113
|
-
* const result = await withAuthErrorHandling(
|
|
114
|
-
* () => oxyServices.updateProfile(updates)
|
|
115
|
-
* );
|
|
116
|
-
*
|
|
117
|
-
* // With retry on auth failure:
|
|
118
|
-
* const result = await withAuthErrorHandling(
|
|
119
|
-
* () => oxyServices.updateProfile(updates),
|
|
120
|
-
* { syncSession, activeSessionId, oxyServices }
|
|
121
|
-
* );
|
|
122
|
-
* ```
|
|
123
|
-
*/
|
|
124
|
-
export async function withAuthErrorHandling<T>(
|
|
125
|
-
apiCall: () => Promise<T>,
|
|
126
|
-
options?: HandleApiErrorOptions
|
|
127
|
-
): Promise<T> {
|
|
128
|
-
try {
|
|
129
|
-
return await apiCall();
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (!isAuthenticationError(error)) {
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// If we have sync capabilities, try to recover
|
|
136
|
-
if (options?.syncSession && options?.activeSessionId && options?.oxyServices) {
|
|
137
|
-
try {
|
|
138
|
-
await options.syncSession();
|
|
139
|
-
await options.oxyServices.getTokenBySession(options.activeSessionId);
|
|
140
|
-
// Retry the API call after refreshing token
|
|
141
|
-
return await apiCall();
|
|
142
|
-
} catch {
|
|
143
|
-
throw new AuthenticationFailedError();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
throw new AuthenticationFailedError();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Combines token validation and auth error handling for a complete authenticated API call.
|
|
153
|
-
* This is the recommended helper for most authenticated API operations.
|
|
154
|
-
*
|
|
155
|
-
* @param oxyServices - The OxyServices instance
|
|
156
|
-
* @param activeSessionId - The active session ID
|
|
157
|
-
* @param apiCall - The API call function to execute
|
|
158
|
-
* @param syncSession - Optional callback to sync session on auth failure
|
|
159
|
-
* @returns The result of the API call
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* ```ts
|
|
163
|
-
* return await authenticatedApiCall(
|
|
164
|
-
* oxyServices,
|
|
165
|
-
* activeSessionId,
|
|
166
|
-
* () => oxyServices.updateProfile(updates)
|
|
167
|
-
* );
|
|
168
|
-
* ```
|
|
169
|
-
*/
|
|
170
|
-
export async function authenticatedApiCall<T>(
|
|
171
|
-
oxyServices: OxyServices,
|
|
172
|
-
activeSessionId: string | null | undefined,
|
|
173
|
-
apiCall: () => Promise<T>,
|
|
174
|
-
syncSession?: () => Promise<unknown>
|
|
175
|
-
): Promise<T> {
|
|
176
|
-
await ensureValidToken(oxyServices, activeSessionId);
|
|
177
|
-
|
|
178
|
-
return withAuthErrorHandling(apiCall, {
|
|
179
|
-
syncSession,
|
|
180
|
-
activeSessionId,
|
|
181
|
-
oxyServices,
|
|
182
|
-
});
|
|
183
|
-
}
|