@oxyhq/auth 2.0.4 → 2.0.5
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/.tsbuildinfo +1 -1
- package/dist/cjs/WebOxyProvider.js +37 -0
- package/dist/cjs/hooks/mutations/useAccountMutations.js +185 -43
- package/dist/cjs/hooks/queryClient.js +136 -92
- package/dist/cjs/hooks/useFileDownloadUrl.js +12 -36
- package/dist/cjs/hooks/useSessionSocket.js +81 -94
- package/dist/cjs/index.js +1 -2
- package/dist/cjs/utils/sessionHelpers.js +3 -1
- package/dist/cjs/utils/storageHelpers.js +36 -10
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/WebOxyProvider.js +38 -1
- package/dist/esm/hooks/mutations/useAccountMutations.js +186 -44
- package/dist/esm/hooks/queryClient.js +132 -89
- package/dist/esm/hooks/useFileDownloadUrl.js +11 -34
- package/dist/esm/hooks/useSessionSocket.js +81 -94
- package/dist/esm/index.js +1 -1
- package/dist/esm/utils/sessionHelpers.js +3 -1
- package/dist/esm/utils/storageHelpers.js +36 -10
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/WebOxyProvider.d.ts +1 -1
- package/dist/types/hooks/mutations/useAccountMutations.d.ts +153 -9
- package/dist/types/hooks/queries/useAccountQueries.d.ts +11 -7
- package/dist/types/hooks/queries/useSecurityQueries.d.ts +2 -2
- package/dist/types/hooks/queries/useServicesQueries.d.ts +7 -5
- package/dist/types/hooks/queryClient.d.ts +24 -10
- package/dist/types/hooks/useAssets.d.ts +1 -1
- package/dist/types/hooks/useFileDownloadUrl.d.ts +2 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/sessionHelpers.d.ts +3 -1
- package/package.json +22 -3
- package/src/WebOxyProvider.tsx +39 -1
- package/src/hooks/mutations/useAccountMutations.ts +230 -57
- package/src/hooks/queryClient.ts +140 -83
- package/src/hooks/useFileDownloadUrl.ts +15 -39
- package/src/hooks/useSessionSocket.ts +123 -91
- package/src/index.ts +1 -1
- package/src/utils/sessionHelpers.ts +3 -1
- package/src/utils/storageHelpers.ts +49 -10
|
@@ -1,42 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useFileDownloadUrl =
|
|
3
|
+
exports.useFileDownloadUrl = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
-
const core_1 = require("@oxyhq/core");
|
|
6
|
-
let oxyInstance = null;
|
|
7
|
-
const setOxyFileUrlInstance = (instance) => {
|
|
8
|
-
oxyInstance = instance;
|
|
9
|
-
};
|
|
10
|
-
exports.setOxyFileUrlInstance = setOxyFileUrlInstance;
|
|
11
5
|
/**
|
|
12
6
|
* Hook to resolve a file's download URL asynchronously.
|
|
13
7
|
*
|
|
14
|
-
* Prefers the provided `oxyServices` instance, falls back to the module-level
|
|
15
|
-
* singleton set via `setOxyFileUrlInstance`.
|
|
16
|
-
*
|
|
17
8
|
* Uses `getFileDownloadUrlAsync` first, falling back to the synchronous
|
|
18
9
|
* `getFileDownloadUrl` if the async call fails.
|
|
19
10
|
*/
|
|
20
|
-
const useFileDownloadUrl = (
|
|
21
|
-
// Support two call signatures:
|
|
22
|
-
// 1. useFileDownloadUrl(oxyServices, fileId, options) — preferred
|
|
23
|
-
// 2. useFileDownloadUrl(fileId, options) — legacy (uses singleton)
|
|
24
|
-
let services;
|
|
25
|
-
let fileId;
|
|
26
|
-
let options;
|
|
27
|
-
if (fileIdOrServices instanceof core_1.OxyServices) {
|
|
28
|
-
services = fileIdOrServices;
|
|
29
|
-
fileId = typeof fileIdOrOptions === 'string' ? fileIdOrOptions : null;
|
|
30
|
-
options = maybeOptions;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
services = oxyInstance;
|
|
34
|
-
fileId = typeof fileIdOrServices === 'string' ? fileIdOrServices : null;
|
|
35
|
-
options = typeof fileIdOrOptions === 'object' && fileIdOrOptions !== null ? fileIdOrOptions : undefined;
|
|
36
|
-
}
|
|
11
|
+
const useFileDownloadUrl = (oxyServices, fileId, options) => {
|
|
37
12
|
const [url, setUrl] = (0, react_1.useState)(null);
|
|
38
13
|
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
39
14
|
const [error, setError] = (0, react_1.useState)(null);
|
|
15
|
+
const variant = options?.variant;
|
|
16
|
+
const expiresIn = options?.expiresIn;
|
|
40
17
|
(0, react_1.useEffect)(() => {
|
|
41
18
|
if (!fileId) {
|
|
42
19
|
setUrl(null);
|
|
@@ -44,25 +21,25 @@ const useFileDownloadUrl = (fileIdOrServices, fileIdOrOptions, maybeOptions) =>
|
|
|
44
21
|
setError(null);
|
|
45
22
|
return;
|
|
46
23
|
}
|
|
47
|
-
if (!
|
|
24
|
+
if (!oxyServices) {
|
|
48
25
|
setUrl(null);
|
|
49
26
|
setLoading(false);
|
|
50
27
|
setError(new Error('OxyServices instance not configured for useFileDownloadUrl'));
|
|
51
28
|
return;
|
|
52
29
|
}
|
|
53
30
|
let cancelled = false;
|
|
54
|
-
const instance =
|
|
31
|
+
const instance = oxyServices;
|
|
32
|
+
const targetFileId = fileId;
|
|
55
33
|
const load = async () => {
|
|
56
34
|
setLoading(true);
|
|
57
35
|
setError(null);
|
|
58
36
|
try {
|
|
59
|
-
const { variant, expiresIn } = options || {};
|
|
60
37
|
let resolvedUrl = null;
|
|
61
38
|
if (typeof instance.getFileDownloadUrlAsync === 'function') {
|
|
62
|
-
resolvedUrl = await instance.getFileDownloadUrlAsync(
|
|
39
|
+
resolvedUrl = await instance.getFileDownloadUrlAsync(targetFileId, variant, expiresIn);
|
|
63
40
|
}
|
|
64
41
|
if (!resolvedUrl && typeof instance.getFileDownloadUrl === 'function') {
|
|
65
|
-
resolvedUrl = instance.getFileDownloadUrl(
|
|
42
|
+
resolvedUrl = instance.getFileDownloadUrl(targetFileId, variant, expiresIn);
|
|
66
43
|
}
|
|
67
44
|
if (!cancelled) {
|
|
68
45
|
setUrl(resolvedUrl || null);
|
|
@@ -72,8 +49,7 @@ const useFileDownloadUrl = (fileIdOrServices, fileIdOrOptions, maybeOptions) =>
|
|
|
72
49
|
// Fallback to sync URL on error where possible
|
|
73
50
|
try {
|
|
74
51
|
if (typeof instance.getFileDownloadUrl === 'function') {
|
|
75
|
-
const
|
|
76
|
-
const fallbackUrl = instance.getFileDownloadUrl(fileId, variant, expiresIn);
|
|
52
|
+
const fallbackUrl = instance.getFileDownloadUrl(targetFileId, variant, expiresIn);
|
|
77
53
|
if (!cancelled) {
|
|
78
54
|
setUrl(fallbackUrl || null);
|
|
79
55
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -82,7 +58,7 @@ const useFileDownloadUrl = (fileIdOrServices, fileIdOrOptions, maybeOptions) =>
|
|
|
82
58
|
}
|
|
83
59
|
}
|
|
84
60
|
catch {
|
|
85
|
-
//
|
|
61
|
+
// Secondary failure: surface the original error below.
|
|
86
62
|
}
|
|
87
63
|
if (!cancelled) {
|
|
88
64
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -98,7 +74,7 @@ const useFileDownloadUrl = (fileIdOrServices, fileIdOrOptions, maybeOptions) =>
|
|
|
98
74
|
return () => {
|
|
99
75
|
cancelled = true;
|
|
100
76
|
};
|
|
101
|
-
}, [fileId,
|
|
77
|
+
}, [fileId, oxyServices, variant, expiresIn]);
|
|
102
78
|
return { url, loading, error };
|
|
103
79
|
};
|
|
104
80
|
exports.useFileDownloadUrl = useFileDownloadUrl;
|
|
@@ -60,7 +60,8 @@ function readTokenFromStorage() {
|
|
|
60
60
|
try {
|
|
61
61
|
return window.localStorage.getItem(LS_ACCESS_TOKEN_KEY);
|
|
62
62
|
}
|
|
63
|
-
catch {
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.warn('[oxy.session-socket] localStorage read failed:', err);
|
|
64
65
|
return null;
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -73,12 +74,12 @@ async function getSocketIO() {
|
|
|
73
74
|
return null;
|
|
74
75
|
_ioLoadAttempted = true;
|
|
75
76
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
_io = (mod.io ?? mod.default);
|
|
77
|
+
const mod = (await Promise.resolve().then(() => __importStar(require('socket.io-client'))));
|
|
78
|
+
_io = mod.io ?? mod.default ?? null;
|
|
79
79
|
return _io;
|
|
80
80
|
}
|
|
81
|
-
catch {
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.warn('[oxy.session-socket] socket.io-client import failed:', err);
|
|
82
83
|
debug.warn('socket.io-client is not installed. useSessionSocket will be disabled. Install it with: bun add socket.io-client');
|
|
83
84
|
return null;
|
|
84
85
|
}
|
|
@@ -145,7 +146,7 @@ function useSessionSocket(options) {
|
|
|
145
146
|
// If no token is available at all, we skip the initial connect and let
|
|
146
147
|
// the storage listener or retry logic connect when a token appears.
|
|
147
148
|
const token = resolveToken();
|
|
148
|
-
|
|
149
|
+
const socket = ioFn(baseURL, {
|
|
149
150
|
transports: ['websocket'],
|
|
150
151
|
autoConnect: !!token, // don't auto-connect when there is no token
|
|
151
152
|
auth: (cb) => {
|
|
@@ -162,7 +163,7 @@ function useSessionSocket(options) {
|
|
|
162
163
|
cb({ token: resolved });
|
|
163
164
|
},
|
|
164
165
|
});
|
|
165
|
-
|
|
166
|
+
socketRef.current = socket;
|
|
166
167
|
// Server auto-joins the user to `user:<userId>` room on connection
|
|
167
168
|
const handleConnect = () => {
|
|
168
169
|
debug.log('Socket connected:', socket.id);
|
|
@@ -198,110 +199,94 @@ function useSessionSocket(options) {
|
|
|
198
199
|
(0, queryKeys_1.invalidateSessionQueries)(queryClientRef.current);
|
|
199
200
|
return Promise.resolve();
|
|
200
201
|
};
|
|
202
|
+
const triggerLocalSignOut = async (toastMessage, errorContext) => {
|
|
203
|
+
if (onRemoteSignOutRef.current) {
|
|
204
|
+
onRemoteSignOutRef.current();
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
sonner_1.toast.info(toastMessage);
|
|
208
|
+
}
|
|
209
|
+
// Clear local state since the server has already removed the session.
|
|
210
|
+
// Await so storage cleanup completes before any subsequent navigation.
|
|
211
|
+
try {
|
|
212
|
+
await clearSessionStateRef.current();
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
216
|
+
core_1.logger.error(`Failed to clear session state after ${errorContext}`, error instanceof Error ? error : new Error(String(error)), { component: 'useSessionSocket' });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
201
220
|
const handleSessionUpdate = async (data) => {
|
|
202
221
|
debug.log('Received session_update:', data);
|
|
203
222
|
const currentActiveSessionId = activeSessionIdRef.current;
|
|
204
223
|
const deviceId = currentDeviceIdRef.current;
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
sonner_1.toast.info('You have been signed out remotely.');
|
|
224
|
+
// Strict whitelist. Every event type that may sign the user out must
|
|
225
|
+
// appear in the switch. Anything unknown falls through to `default`,
|
|
226
|
+
// which only logs in dev. This guards against future server-side event
|
|
227
|
+
// additions (e.g. `session_created` after a successful sign-in)
|
|
228
|
+
// accidentally triggering sign-out via a fallback branch that compares
|
|
229
|
+
// `data.sessionId === currentActiveSessionId` — that branch would match
|
|
230
|
+
// the user's NEW session id and trigger an instant remote sign-out
|
|
231
|
+
// toast on every login.
|
|
232
|
+
switch (data.type) {
|
|
233
|
+
case 'session_removed': {
|
|
234
|
+
if (data.sessionId && onSessionRemovedRef.current) {
|
|
235
|
+
onSessionRemovedRef.current(data.sessionId);
|
|
218
236
|
}
|
|
219
|
-
|
|
220
|
-
await
|
|
237
|
+
if (data.sessionId && data.sessionId === currentActiveSessionId) {
|
|
238
|
+
await triggerLocalSignOut('You have been signed out remotely.', 'session_removed');
|
|
221
239
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
core_1.logger.error('Failed to clear session state after session_removed', error instanceof Error ? error : new Error(String(error)), { component: 'useSessionSocket' });
|
|
225
|
-
}
|
|
240
|
+
else {
|
|
241
|
+
refreshSessions();
|
|
226
242
|
}
|
|
243
|
+
break;
|
|
227
244
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// Track all removed sessions from this device
|
|
234
|
-
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
235
|
-
for (const sessionId of data.sessionIds) {
|
|
236
|
-
onSessionRemovedRef.current(sessionId);
|
|
245
|
+
case 'device_removed': {
|
|
246
|
+
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
247
|
+
for (const sessionId of data.sessionIds) {
|
|
248
|
+
onSessionRemovedRef.current(sessionId);
|
|
249
|
+
}
|
|
237
250
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (data.deviceId && data.deviceId === deviceId) {
|
|
241
|
-
if (onRemoteSignOutRef.current) {
|
|
242
|
-
onRemoteSignOutRef.current();
|
|
251
|
+
if (data.deviceId && deviceId && data.deviceId === deviceId) {
|
|
252
|
+
await triggerLocalSignOut('This device has been removed. You have been signed out.', 'device_removed');
|
|
243
253
|
}
|
|
244
254
|
else {
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
try {
|
|
248
|
-
await clearSessionStateRef.current();
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
251
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
252
|
-
core_1.logger.error('Failed to clear session state after device_removed', error instanceof Error ? error : new Error(String(error)), { component: 'useSessionSocket' });
|
|
253
|
-
}
|
|
255
|
+
refreshSessions();
|
|
254
256
|
}
|
|
257
|
+
break;
|
|
255
258
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// Track all removed sessions
|
|
262
|
-
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
263
|
-
for (const sessionId of data.sessionIds) {
|
|
264
|
-
onSessionRemovedRef.current(sessionId);
|
|
259
|
+
case 'sessions_removed': {
|
|
260
|
+
if (data.sessionIds && onSessionRemovedRef.current) {
|
|
261
|
+
for (const sessionId of data.sessionIds) {
|
|
262
|
+
onSessionRemovedRef.current(sessionId);
|
|
263
|
+
}
|
|
265
264
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
onRemoteSignOutRef.current();
|
|
265
|
+
if (data.sessionIds &&
|
|
266
|
+
currentActiveSessionId &&
|
|
267
|
+
data.sessionIds.includes(currentActiveSessionId)) {
|
|
268
|
+
await triggerLocalSignOut('You have been signed out remotely.', 'sessions_removed');
|
|
271
269
|
}
|
|
272
270
|
else {
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
try {
|
|
276
|
-
await clearSessionStateRef.current();
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
280
|
-
core_1.logger.error('Failed to clear session state after sessions_removed', error instanceof Error ? error : new Error(String(error)), { component: 'useSessionSocket' });
|
|
281
|
-
}
|
|
271
|
+
refreshSessions();
|
|
282
272
|
}
|
|
273
|
+
break;
|
|
283
274
|
}
|
|
284
|
-
|
|
275
|
+
case 'session_created':
|
|
276
|
+
case 'session_update': {
|
|
277
|
+
// Lifecycle event for the current user. Just resync the sessions
|
|
278
|
+
// list — never sign out.
|
|
285
279
|
refreshSessions();
|
|
280
|
+
break;
|
|
286
281
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (onRemoteSignOutRef.current) {
|
|
294
|
-
onRemoteSignOutRef.current();
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
sonner_1.toast.info('You have been signed out remotely.');
|
|
298
|
-
}
|
|
299
|
-
try {
|
|
300
|
-
await clearSessionStateRef.current();
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
debug.error('Failed to clear session state after session_update:', error);
|
|
282
|
+
default: {
|
|
283
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
284
|
+
core_1.logger.warn('Unknown session socket event type', {
|
|
285
|
+
component: 'useSessionSocket',
|
|
286
|
+
type: data.type,
|
|
287
|
+
});
|
|
304
288
|
}
|
|
289
|
+
break;
|
|
305
290
|
}
|
|
306
291
|
}
|
|
307
292
|
};
|
|
@@ -329,12 +314,14 @@ function useSessionSocket(options) {
|
|
|
329
314
|
if (authRetryTimer) {
|
|
330
315
|
clearTimeout(authRetryTimer);
|
|
331
316
|
}
|
|
332
|
-
|
|
317
|
+
const currentSocket = socketRef.current;
|
|
318
|
+
if (currentSocket) {
|
|
333
319
|
// Remove cross-tab storage listener
|
|
334
|
-
|
|
335
|
-
|
|
320
|
+
const storageHandler = currentSocket.__oxyStorageHandler;
|
|
321
|
+
if (typeof window !== 'undefined' && storageHandler) {
|
|
322
|
+
window.removeEventListener('storage', storageHandler);
|
|
336
323
|
}
|
|
337
|
-
|
|
324
|
+
currentSocket.disconnect();
|
|
338
325
|
socketRef.current = null;
|
|
339
326
|
}
|
|
340
327
|
};
|
package/dist/cjs/index.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
27
|
exports.useAssets = exports.useSessionSocket = exports.isWebBrowser = exports.useWebSSO = 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.useFollowStore = exports.useAccountLoadingSession = exports.useAccountError = exports.useAccountLoading = exports.useAccounts = exports.useAccountStore = 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 = exports.isInvalidSessionError = exports.handleAuthError = exports.useFileFiltering = exports.useFollowerCounts = exports.useFollow = exports.
|
|
28
|
+
exports.extractErrorMessage = exports.isTimeoutOrNetworkError = exports.isInvalidSessionError = exports.handleAuthError = exports.useFileFiltering = exports.useFollowerCounts = exports.useFollow = exports.useFileDownloadUrl = exports.setOxyAssetInstance = 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; } });
|
|
@@ -95,7 +95,6 @@ Object.defineProperty(exports, "useAssets", { enumerable: true, get: function ()
|
|
|
95
95
|
Object.defineProperty(exports, "setOxyAssetInstance", { enumerable: true, get: function () { return useAssets_1.setOxyAssetInstance; } });
|
|
96
96
|
var useFileDownloadUrl_1 = require("./hooks/useFileDownloadUrl");
|
|
97
97
|
Object.defineProperty(exports, "useFileDownloadUrl", { enumerable: true, get: function () { return useFileDownloadUrl_1.useFileDownloadUrl; } });
|
|
98
|
-
Object.defineProperty(exports, "setOxyFileUrlInstance", { enumerable: true, get: function () { return useFileDownloadUrl_1.setOxyFileUrlInstance; } });
|
|
99
98
|
var useFollow_1 = require("./hooks/useFollow");
|
|
100
99
|
Object.defineProperty(exports, "useFollow", { enumerable: true, get: function () { return useFollow_1.useFollow; } });
|
|
101
100
|
Object.defineProperty(exports, "useFollowerCounts", { enumerable: true, get: function () { return useFollow_1.useFollowerCounts; } });
|
|
@@ -25,7 +25,9 @@ const mapSessionsToClient = (sessions, fallbackDeviceId, fallbackUserId) => {
|
|
|
25
25
|
};
|
|
26
26
|
exports.mapSessionsToClient = mapSessionsToClient;
|
|
27
27
|
/**
|
|
28
|
-
* Fetch device sessions
|
|
28
|
+
* Fetch device sessions, falling back to the per-user session endpoint
|
|
29
|
+
* if the device endpoint is unavailable (older API versions or disabled
|
|
30
|
+
* device-grouping feature flag).
|
|
29
31
|
*
|
|
30
32
|
* @param oxyServices - Oxy service instance
|
|
31
33
|
* @param sessionId - Session identifier to fetch
|
|
@@ -41,7 +41,8 @@ const MEMORY_STORAGE = () => {
|
|
|
41
41
|
const store = new Map();
|
|
42
42
|
return {
|
|
43
43
|
async getItem(key) {
|
|
44
|
-
|
|
44
|
+
const value = store.get(key);
|
|
45
|
+
return value === undefined ? null : value;
|
|
45
46
|
},
|
|
46
47
|
async setItem(key, value) {
|
|
47
48
|
store.set(key, value);
|
|
@@ -67,7 +68,8 @@ const createWebStorage = () => {
|
|
|
67
68
|
try {
|
|
68
69
|
return window.localStorage.getItem(key);
|
|
69
70
|
}
|
|
70
|
-
catch {
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.warn('[oxy.storage] localStorage.getItem failed:', err);
|
|
71
73
|
return null;
|
|
72
74
|
}
|
|
73
75
|
},
|
|
@@ -75,29 +77,44 @@ const createWebStorage = () => {
|
|
|
75
77
|
try {
|
|
76
78
|
window.localStorage.setItem(key, value);
|
|
77
79
|
}
|
|
78
|
-
catch {
|
|
79
|
-
//
|
|
80
|
+
catch (err) {
|
|
81
|
+
// Quota exceeded or storage disabled (e.g., Safari private mode).
|
|
82
|
+
// Surface to logs so it is debuggable, but do not throw so callers
|
|
83
|
+
// can keep functioning with degraded persistence.
|
|
84
|
+
console.warn('[oxy.storage] localStorage.setItem failed:', err);
|
|
80
85
|
}
|
|
81
86
|
},
|
|
82
87
|
async removeItem(key) {
|
|
83
88
|
try {
|
|
84
89
|
window.localStorage.removeItem(key);
|
|
85
90
|
}
|
|
86
|
-
catch {
|
|
87
|
-
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.warn('[oxy.storage] localStorage.removeItem failed:', err);
|
|
88
93
|
}
|
|
89
94
|
},
|
|
90
95
|
async clear() {
|
|
91
96
|
try {
|
|
92
97
|
window.localStorage.clear();
|
|
93
98
|
}
|
|
94
|
-
catch {
|
|
95
|
-
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.warn('[oxy.storage] localStorage.clear failed:', err);
|
|
96
101
|
}
|
|
97
102
|
},
|
|
98
103
|
};
|
|
99
104
|
};
|
|
100
105
|
let asyncStorageInstance = null;
|
|
106
|
+
/**
|
|
107
|
+
* Type guard verifying that an imported value exposes the AsyncStorage API.
|
|
108
|
+
*/
|
|
109
|
+
const isAsyncStorageLike = (value) => {
|
|
110
|
+
if (typeof value !== 'object' || value === null)
|
|
111
|
+
return false;
|
|
112
|
+
const candidate = value;
|
|
113
|
+
return (typeof candidate.getItem === 'function' &&
|
|
114
|
+
typeof candidate.setItem === 'function' &&
|
|
115
|
+
typeof candidate.removeItem === 'function' &&
|
|
116
|
+
typeof candidate.clear === 'function');
|
|
117
|
+
};
|
|
101
118
|
/**
|
|
102
119
|
* Lazily import React Native AsyncStorage implementation.
|
|
103
120
|
*/
|
|
@@ -108,8 +125,17 @@ const createNativeStorage = async () => {
|
|
|
108
125
|
try {
|
|
109
126
|
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
110
127
|
const moduleName = '@react-native-async-storage/async-storage';
|
|
111
|
-
const asyncStorageModule = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
|
|
112
|
-
|
|
128
|
+
const asyncStorageModule = (await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s))));
|
|
129
|
+
const candidate = asyncStorageModule.default;
|
|
130
|
+
if (!isAsyncStorageLike(candidate)) {
|
|
131
|
+
throw new Error('AsyncStorage default export does not match expected API');
|
|
132
|
+
}
|
|
133
|
+
asyncStorageInstance = {
|
|
134
|
+
getItem: (key) => candidate.getItem(key),
|
|
135
|
+
setItem: (key, value) => candidate.setItem(key, value),
|
|
136
|
+
removeItem: (key) => candidate.removeItem(key),
|
|
137
|
+
clear: () => candidate.clear(),
|
|
138
|
+
};
|
|
113
139
|
return asyncStorageInstance;
|
|
114
140
|
}
|
|
115
141
|
catch (error) {
|