@oxyhq/auth 1.2.0 → 2.0.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.
@@ -9,29 +9,41 @@ const socket_io_client_1 = __importDefault(require("socket.io-client"));
9
9
  const sonner_1 = require("sonner");
10
10
  const core_1 = require("@oxyhq/core");
11
11
  const core_2 = require("@oxyhq/core");
12
+ const WebOxyProvider_1 = require("../WebOxyProvider");
13
+ const react_query_1 = require("@tanstack/react-query");
14
+ const useServicesQueries_1 = require("./queries/useServicesQueries");
15
+ const queryKeys_1 = require("./queries/queryKeys");
12
16
  const debug = (0, core_2.createDebugLogger)('SessionSocket');
13
- function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, clearSessionState, baseURL, getAccessToken, onRemoteSignOut, onSessionRemoved }) {
17
+ function useSessionSocket(options) {
18
+ const { user, activeSessionId, oxyServices, signOut, clearSessionState } = (0, WebOxyProvider_1.useWebOxy)();
19
+ const queryClient = (0, react_query_1.useQueryClient)();
20
+ const userId = user?.id ?? null;
21
+ const baseURL = oxyServices.getBaseURL();
22
+ // Derive currentDeviceId from sessions query
23
+ const { data: sessions } = (0, useServicesQueries_1.useSessions)(userId ?? undefined);
24
+ const currentDeviceId = (0, react_1.useMemo)(() => {
25
+ if (!sessions || !activeSessionId)
26
+ return null;
27
+ const active = sessions.find((s) => s.sessionId === activeSessionId);
28
+ return active?.deviceId ?? null;
29
+ }, [sessions, activeSessionId]);
14
30
  const socketRef = (0, react_1.useRef)(null);
15
- // Store callbacks in refs to avoid reconnecting when they change
16
- const refreshSessionsRef = (0, react_1.useRef)(refreshSessions);
17
- const logoutRef = (0, react_1.useRef)(logout);
31
+ // Store callbacks and values in refs to avoid reconnecting when they change
18
32
  const clearSessionStateRef = (0, react_1.useRef)(clearSessionState);
19
- const onRemoteSignOutRef = (0, react_1.useRef)(onRemoteSignOut);
20
- const onSessionRemovedRef = (0, react_1.useRef)(onSessionRemoved);
33
+ const onRemoteSignOutRef = (0, react_1.useRef)(options?.onRemoteSignOut);
34
+ const onSessionRemovedRef = (0, react_1.useRef)(options?.onSessionRemoved);
21
35
  const activeSessionIdRef = (0, react_1.useRef)(activeSessionId);
22
36
  const currentDeviceIdRef = (0, react_1.useRef)(currentDeviceId);
23
- const getAccessTokenRef = (0, react_1.useRef)(getAccessToken);
24
- // Update refs when callbacks change
37
+ const queryClientRef = (0, react_1.useRef)(queryClient);
38
+ // Update refs when values change
25
39
  (0, react_1.useEffect)(() => {
26
- refreshSessionsRef.current = refreshSessions;
27
- logoutRef.current = logout;
28
40
  clearSessionStateRef.current = clearSessionState;
29
- onRemoteSignOutRef.current = onRemoteSignOut;
30
- onSessionRemovedRef.current = onSessionRemoved;
41
+ onRemoteSignOutRef.current = options?.onRemoteSignOut;
42
+ onSessionRemovedRef.current = options?.onSessionRemoved;
31
43
  activeSessionIdRef.current = activeSessionId;
32
44
  currentDeviceIdRef.current = currentDeviceId;
33
- getAccessTokenRef.current = getAccessToken;
34
- }, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId, getAccessToken]);
45
+ queryClientRef.current = queryClient;
46
+ }, [clearSessionState, options?.onRemoteSignOut, options?.onSessionRemoved, activeSessionId, currentDeviceId, queryClient]);
35
47
  (0, react_1.useEffect)(() => {
36
48
  if (!userId || !baseURL) {
37
49
  // Clean up if userId or baseURL becomes invalid
@@ -50,7 +62,7 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
50
62
  socketRef.current = (0, socket_io_client_1.default)(baseURL, {
51
63
  transports: ['websocket'],
52
64
  auth: (cb) => {
53
- const token = getAccessTokenRef.current();
65
+ const token = oxyServices.getAccessToken();
54
66
  cb({ token: token ?? '' });
55
67
  },
56
68
  });
@@ -59,10 +71,14 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
59
71
  const handleConnect = () => {
60
72
  debug.log('Socket connected:', socket.id);
61
73
  };
74
+ const refreshSessions = () => {
75
+ (0, queryKeys_1.invalidateSessionQueries)(queryClientRef.current);
76
+ return Promise.resolve();
77
+ };
62
78
  const handleSessionUpdate = async (data) => {
63
79
  debug.log('Received session_update:', data);
64
80
  const currentActiveSessionId = activeSessionIdRef.current;
65
- const currentDeviceId = currentDeviceIdRef.current;
81
+ const deviceId = currentDeviceIdRef.current;
66
82
  // Handle different event types
67
83
  if (data.type === 'session_removed') {
68
84
  // Track removed session
@@ -77,8 +93,6 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
77
93
  else {
78
94
  sonner_1.toast.info('You have been signed out remotely.');
79
95
  }
80
- // Use clearSessionState since session was already removed server-side
81
- // Await to ensure storage cleanup completes before continuing
82
96
  try {
83
97
  await clearSessionStateRef.current();
84
98
  }
@@ -89,13 +103,7 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
89
103
  }
90
104
  }
91
105
  else {
92
- // Otherwise, just refresh the sessions list (with error handling)
93
- refreshSessionsRef.current().catch((error) => {
94
- // Silently handle errors from refresh - they're expected if sessions were removed
95
- if (__DEV__) {
96
- core_1.logger.debug('Failed to refresh sessions after session_removed', { component: 'useSessionSocket' }, error);
97
- }
98
- });
106
+ refreshSessions();
99
107
  }
100
108
  }
101
109
  else if (data.type === 'device_removed') {
@@ -106,15 +114,13 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
106
114
  }
107
115
  }
108
116
  // If the removed deviceId matches the current device, immediately clear state
109
- if (data.deviceId && data.deviceId === currentDeviceId) {
117
+ if (data.deviceId && data.deviceId === deviceId) {
110
118
  if (onRemoteSignOutRef.current) {
111
119
  onRemoteSignOutRef.current();
112
120
  }
113
121
  else {
114
122
  sonner_1.toast.info('This device has been removed. You have been signed out.');
115
123
  }
116
- // Use clearSessionState since sessions were already removed server-side
117
- // Await to ensure storage cleanup completes before continuing
118
124
  try {
119
125
  await clearSessionStateRef.current();
120
126
  }
@@ -125,13 +131,7 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
125
131
  }
126
132
  }
127
133
  else {
128
- // Otherwise, refresh sessions and device list (with error handling)
129
- refreshSessionsRef.current().catch((error) => {
130
- // Silently handle errors from refresh - they're expected if sessions were removed
131
- if (__DEV__) {
132
- core_1.logger.debug('Failed to refresh sessions after device_removed', { component: 'useSessionSocket' }, error);
133
- }
134
- });
134
+ refreshSessions();
135
135
  }
136
136
  }
137
137
  else if (data.type === 'sessions_removed') {
@@ -149,8 +149,6 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
149
149
  else {
150
150
  sonner_1.toast.info('You have been signed out remotely.');
151
151
  }
152
- // Use clearSessionState since sessions were already removed server-side
153
- // Await to ensure storage cleanup completes before continuing
154
152
  try {
155
153
  await clearSessionStateRef.current();
156
154
  }
@@ -161,23 +159,12 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
161
159
  }
162
160
  }
163
161
  else {
164
- // Otherwise, refresh sessions list (with error handling)
165
- refreshSessionsRef.current().catch((error) => {
166
- // Silently handle errors from refresh - they're expected if sessions were removed
167
- if (__DEV__) {
168
- core_1.logger.debug('Failed to refresh sessions after sessions_removed', { component: 'useSessionSocket' }, error);
169
- }
170
- });
162
+ refreshSessions();
171
163
  }
172
164
  }
173
165
  else {
174
- // For other event types (e.g., session_created), refresh sessions (with error handling)
175
- refreshSessionsRef.current().catch((error) => {
176
- // Log but don't throw - refresh errors shouldn't break the socket handler
177
- if (__DEV__) {
178
- core_1.logger.debug('Failed to refresh sessions after session_update', { component: 'useSessionSocket' }, error);
179
- }
180
- });
166
+ // For other event types (e.g., session_created), refresh sessions
167
+ refreshSessions();
181
168
  // If the current session was logged out (legacy behavior), handle it specially
182
169
  if (data.sessionId === currentActiveSessionId) {
183
170
  if (onRemoteSignOutRef.current) {
@@ -186,8 +173,6 @@ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSes
186
173
  else {
187
174
  sonner_1.toast.info('You have been signed out remotely.');
188
175
  }
189
- // Use clearSessionState since session was already removed server-side
190
- // Await to ensure storage cleanup completes before continuing
191
176
  try {
192
177
  await clearSessionStateRef.current();
193
178
  }
@@ -1,31 +1,43 @@
1
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useRef, useMemo } from 'react';
2
2
  import io from 'socket.io-client';
3
3
  import { toast } from 'sonner';
4
4
  import { logger } from '@oxyhq/core';
5
5
  import { createDebugLogger } from '@oxyhq/core';
6
+ import { useWebOxy } from '../WebOxyProvider';
7
+ import { useQueryClient } from '@tanstack/react-query';
8
+ import { useSessions } from './queries/useServicesQueries';
9
+ import { invalidateSessionQueries } from './queries/queryKeys';
6
10
  const debug = createDebugLogger('SessionSocket');
7
- export function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, clearSessionState, baseURL, getAccessToken, onRemoteSignOut, onSessionRemoved }) {
11
+ export function useSessionSocket(options) {
12
+ const { user, activeSessionId, oxyServices, signOut, clearSessionState } = useWebOxy();
13
+ const queryClient = useQueryClient();
14
+ const userId = user?.id ?? null;
15
+ const baseURL = oxyServices.getBaseURL();
16
+ // Derive currentDeviceId from sessions query
17
+ const { data: sessions } = useSessions(userId ?? undefined);
18
+ const currentDeviceId = useMemo(() => {
19
+ if (!sessions || !activeSessionId)
20
+ return null;
21
+ const active = sessions.find((s) => s.sessionId === activeSessionId);
22
+ return active?.deviceId ?? null;
23
+ }, [sessions, activeSessionId]);
8
24
  const socketRef = useRef(null);
9
- // Store callbacks in refs to avoid reconnecting when they change
10
- const refreshSessionsRef = useRef(refreshSessions);
11
- const logoutRef = useRef(logout);
25
+ // Store callbacks and values in refs to avoid reconnecting when they change
12
26
  const clearSessionStateRef = useRef(clearSessionState);
13
- const onRemoteSignOutRef = useRef(onRemoteSignOut);
14
- const onSessionRemovedRef = useRef(onSessionRemoved);
27
+ const onRemoteSignOutRef = useRef(options?.onRemoteSignOut);
28
+ const onSessionRemovedRef = useRef(options?.onSessionRemoved);
15
29
  const activeSessionIdRef = useRef(activeSessionId);
16
30
  const currentDeviceIdRef = useRef(currentDeviceId);
17
- const getAccessTokenRef = useRef(getAccessToken);
18
- // Update refs when callbacks change
31
+ const queryClientRef = useRef(queryClient);
32
+ // Update refs when values change
19
33
  useEffect(() => {
20
- refreshSessionsRef.current = refreshSessions;
21
- logoutRef.current = logout;
22
34
  clearSessionStateRef.current = clearSessionState;
23
- onRemoteSignOutRef.current = onRemoteSignOut;
24
- onSessionRemovedRef.current = onSessionRemoved;
35
+ onRemoteSignOutRef.current = options?.onRemoteSignOut;
36
+ onSessionRemovedRef.current = options?.onSessionRemoved;
25
37
  activeSessionIdRef.current = activeSessionId;
26
38
  currentDeviceIdRef.current = currentDeviceId;
27
- getAccessTokenRef.current = getAccessToken;
28
- }, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId, getAccessToken]);
39
+ queryClientRef.current = queryClient;
40
+ }, [clearSessionState, options?.onRemoteSignOut, options?.onSessionRemoved, activeSessionId, currentDeviceId, queryClient]);
29
41
  useEffect(() => {
30
42
  if (!userId || !baseURL) {
31
43
  // Clean up if userId or baseURL becomes invalid
@@ -44,7 +56,7 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
44
56
  socketRef.current = io(baseURL, {
45
57
  transports: ['websocket'],
46
58
  auth: (cb) => {
47
- const token = getAccessTokenRef.current();
59
+ const token = oxyServices.getAccessToken();
48
60
  cb({ token: token ?? '' });
49
61
  },
50
62
  });
@@ -53,10 +65,14 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
53
65
  const handleConnect = () => {
54
66
  debug.log('Socket connected:', socket.id);
55
67
  };
68
+ const refreshSessions = () => {
69
+ invalidateSessionQueries(queryClientRef.current);
70
+ return Promise.resolve();
71
+ };
56
72
  const handleSessionUpdate = async (data) => {
57
73
  debug.log('Received session_update:', data);
58
74
  const currentActiveSessionId = activeSessionIdRef.current;
59
- const currentDeviceId = currentDeviceIdRef.current;
75
+ const deviceId = currentDeviceIdRef.current;
60
76
  // Handle different event types
61
77
  if (data.type === 'session_removed') {
62
78
  // Track removed session
@@ -71,8 +87,6 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
71
87
  else {
72
88
  toast.info('You have been signed out remotely.');
73
89
  }
74
- // Use clearSessionState since session was already removed server-side
75
- // Await to ensure storage cleanup completes before continuing
76
90
  try {
77
91
  await clearSessionStateRef.current();
78
92
  }
@@ -83,13 +97,7 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
83
97
  }
84
98
  }
85
99
  else {
86
- // Otherwise, just refresh the sessions list (with error handling)
87
- refreshSessionsRef.current().catch((error) => {
88
- // Silently handle errors from refresh - they're expected if sessions were removed
89
- if (__DEV__) {
90
- logger.debug('Failed to refresh sessions after session_removed', { component: 'useSessionSocket' }, error);
91
- }
92
- });
100
+ refreshSessions();
93
101
  }
94
102
  }
95
103
  else if (data.type === 'device_removed') {
@@ -100,15 +108,13 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
100
108
  }
101
109
  }
102
110
  // If the removed deviceId matches the current device, immediately clear state
103
- if (data.deviceId && data.deviceId === currentDeviceId) {
111
+ if (data.deviceId && data.deviceId === deviceId) {
104
112
  if (onRemoteSignOutRef.current) {
105
113
  onRemoteSignOutRef.current();
106
114
  }
107
115
  else {
108
116
  toast.info('This device has been removed. You have been signed out.');
109
117
  }
110
- // Use clearSessionState since sessions were already removed server-side
111
- // Await to ensure storage cleanup completes before continuing
112
118
  try {
113
119
  await clearSessionStateRef.current();
114
120
  }
@@ -119,13 +125,7 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
119
125
  }
120
126
  }
121
127
  else {
122
- // Otherwise, refresh sessions and device list (with error handling)
123
- refreshSessionsRef.current().catch((error) => {
124
- // Silently handle errors from refresh - they're expected if sessions were removed
125
- if (__DEV__) {
126
- logger.debug('Failed to refresh sessions after device_removed', { component: 'useSessionSocket' }, error);
127
- }
128
- });
128
+ refreshSessions();
129
129
  }
130
130
  }
131
131
  else if (data.type === 'sessions_removed') {
@@ -143,8 +143,6 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
143
143
  else {
144
144
  toast.info('You have been signed out remotely.');
145
145
  }
146
- // Use clearSessionState since sessions were already removed server-side
147
- // Await to ensure storage cleanup completes before continuing
148
146
  try {
149
147
  await clearSessionStateRef.current();
150
148
  }
@@ -155,23 +153,12 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
155
153
  }
156
154
  }
157
155
  else {
158
- // Otherwise, refresh sessions list (with error handling)
159
- refreshSessionsRef.current().catch((error) => {
160
- // Silently handle errors from refresh - they're expected if sessions were removed
161
- if (__DEV__) {
162
- logger.debug('Failed to refresh sessions after sessions_removed', { component: 'useSessionSocket' }, error);
163
- }
164
- });
156
+ refreshSessions();
165
157
  }
166
158
  }
167
159
  else {
168
- // For other event types (e.g., session_created), refresh sessions (with error handling)
169
- refreshSessionsRef.current().catch((error) => {
170
- // Log but don't throw - refresh errors shouldn't break the socket handler
171
- if (__DEV__) {
172
- logger.debug('Failed to refresh sessions after session_update', { component: 'useSessionSocket' }, error);
173
- }
174
- });
160
+ // For other event types (e.g., session_created), refresh sessions
161
+ refreshSessions();
175
162
  // If the current session was logged out (legacy behavior), handle it specially
176
163
  if (data.sessionId === currentActiveSessionId) {
177
164
  if (onRemoteSignOutRef.current) {
@@ -180,8 +167,6 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
180
167
  else {
181
168
  toast.info('You have been signed out remotely.');
182
169
  }
183
- // Use clearSessionState since session was already removed server-side
184
- // Await to ensure storage cleanup completes before continuing
185
170
  try {
186
171
  await clearSessionStateRef.current();
187
172
  }
@@ -1,14 +1,5 @@
1
- interface UseSessionSocketProps {
2
- userId: string | null | undefined;
3
- activeSessionId: string | null | undefined;
4
- currentDeviceId: string | null | undefined;
5
- refreshSessions: () => Promise<void>;
6
- logout: () => Promise<void>;
7
- clearSessionState: () => Promise<void>;
8
- baseURL: string;
9
- getAccessToken: () => string | null;
1
+ export interface UseSessionSocketOptions {
10
2
  onRemoteSignOut?: () => void;
11
3
  onSessionRemoved?: (sessionId: string) => void;
12
4
  }
13
- export declare function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, clearSessionState, baseURL, getAccessToken, onRemoteSignOut, onSessionRemoved }: UseSessionSocketProps): void;
14
- export {};
5
+ export declare function useSessionSocket(options?: UseSessionSocketOptions): void;
@@ -31,6 +31,7 @@ export { useUpdateProfile, useUploadAvatar, useUpdateAccountSettings, useUpdateP
31
31
  export { createProfileMutation, createGenericMutation, } from './hooks/mutations/mutationFactory';
32
32
  export type { ProfileMutationConfig, GenericMutationConfig, } from './hooks/mutations/mutationFactory';
33
33
  export { useSessionSocket } from './hooks/useSessionSocket';
34
+ export type { UseSessionSocketOptions } from './hooks/useSessionSocket';
34
35
  export { useAssets, setOxyAssetInstance } from './hooks/useAssets';
35
36
  export { useFileDownloadUrl, setOxyFileUrlInstance } from './hooks/useFileDownloadUrl';
36
37
  export { useFollow, useFollowerCounts } from './hooks/useFollow';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/auth",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "OxyHQ Web Authentication SDK — headless auth with React hooks for Next.js, Vite, and web apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -1,48 +1,54 @@
1
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useRef, useMemo } from 'react';
2
2
  import io from 'socket.io-client';
3
3
  import { toast } from 'sonner';
4
4
  import { logger } from '@oxyhq/core';
5
5
  import { createDebugLogger } from '@oxyhq/core';
6
+ import { useWebOxy } from '../WebOxyProvider';
7
+ import { useQueryClient } from '@tanstack/react-query';
8
+ import { useSessions } from './queries/useServicesQueries';
9
+ import { invalidateSessionQueries } from './queries/queryKeys';
6
10
 
7
11
  const debug = createDebugLogger('SessionSocket');
8
12
 
9
- interface UseSessionSocketProps {
10
- userId: string | null | undefined;
11
- activeSessionId: string | null | undefined;
12
- currentDeviceId: string | null | undefined;
13
- refreshSessions: () => Promise<void>;
14
- logout: () => Promise<void>;
15
- clearSessionState: () => Promise<void>;
16
- baseURL: string;
17
- getAccessToken: () => string | null;
13
+ export interface UseSessionSocketOptions {
18
14
  onRemoteSignOut?: () => void;
19
15
  onSessionRemoved?: (sessionId: string) => void;
20
16
  }
21
17
 
22
- export function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, clearSessionState, baseURL, getAccessToken, onRemoteSignOut, onSessionRemoved }: UseSessionSocketProps) {
18
+ export function useSessionSocket(options?: UseSessionSocketOptions) {
19
+ const { user, activeSessionId, oxyServices, signOut, clearSessionState } = useWebOxy();
20
+ const queryClient = useQueryClient();
21
+
22
+ const userId = user?.id ?? null;
23
+ const baseURL = oxyServices.getBaseURL();
24
+
25
+ // Derive currentDeviceId from sessions query
26
+ const { data: sessions } = useSessions(userId ?? undefined);
27
+ const currentDeviceId = useMemo(() => {
28
+ if (!sessions || !activeSessionId) return null;
29
+ const active = sessions.find((s) => s.sessionId === activeSessionId);
30
+ return active?.deviceId ?? null;
31
+ }, [sessions, activeSessionId]);
32
+
23
33
  const socketRef = useRef<any>(null);
24
34
 
25
- // Store callbacks in refs to avoid reconnecting when they change
26
- const refreshSessionsRef = useRef(refreshSessions);
27
- const logoutRef = useRef(logout);
35
+ // Store callbacks and values in refs to avoid reconnecting when they change
28
36
  const clearSessionStateRef = useRef(clearSessionState);
29
- const onRemoteSignOutRef = useRef(onRemoteSignOut);
30
- const onSessionRemovedRef = useRef(onSessionRemoved);
37
+ const onRemoteSignOutRef = useRef(options?.onRemoteSignOut);
38
+ const onSessionRemovedRef = useRef(options?.onSessionRemoved);
31
39
  const activeSessionIdRef = useRef(activeSessionId);
32
40
  const currentDeviceIdRef = useRef(currentDeviceId);
33
- const getAccessTokenRef = useRef(getAccessToken);
41
+ const queryClientRef = useRef(queryClient);
34
42
 
35
- // Update refs when callbacks change
43
+ // Update refs when values change
36
44
  useEffect(() => {
37
- refreshSessionsRef.current = refreshSessions;
38
- logoutRef.current = logout;
39
45
  clearSessionStateRef.current = clearSessionState;
40
- onRemoteSignOutRef.current = onRemoteSignOut;
41
- onSessionRemovedRef.current = onSessionRemoved;
46
+ onRemoteSignOutRef.current = options?.onRemoteSignOut;
47
+ onSessionRemovedRef.current = options?.onSessionRemoved;
42
48
  activeSessionIdRef.current = activeSessionId;
43
49
  currentDeviceIdRef.current = currentDeviceId;
44
- getAccessTokenRef.current = getAccessToken;
45
- }, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId, getAccessToken]);
50
+ queryClientRef.current = queryClient;
51
+ }, [clearSessionState, options?.onRemoteSignOut, options?.onSessionRemoved, activeSessionId, currentDeviceId, queryClient]);
46
52
 
47
53
  useEffect(() => {
48
54
  if (!userId || !baseURL) {
@@ -64,7 +70,7 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
64
70
  socketRef.current = io(baseURL, {
65
71
  transports: ['websocket'],
66
72
  auth: (cb: (data: { token: string }) => void) => {
67
- const token = getAccessTokenRef.current();
73
+ const token = oxyServices.getAccessToken();
68
74
  cb({ token: token ?? '' });
69
75
  },
70
76
  });
@@ -75,6 +81,11 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
75
81
  debug.log('Socket connected:', socket.id);
76
82
  };
77
83
 
84
+ const refreshSessions = () => {
85
+ invalidateSessionQueries(queryClientRef.current);
86
+ return Promise.resolve();
87
+ };
88
+
78
89
  const handleSessionUpdate = async (data: {
79
90
  type: string;
80
91
  sessionId?: string;
@@ -82,17 +93,17 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
82
93
  sessionIds?: string[]
83
94
  }) => {
84
95
  debug.log('Received session_update:', data);
85
-
96
+
86
97
  const currentActiveSessionId = activeSessionIdRef.current;
87
- const currentDeviceId = currentDeviceIdRef.current;
88
-
98
+ const deviceId = currentDeviceIdRef.current;
99
+
89
100
  // Handle different event types
90
101
  if (data.type === 'session_removed') {
91
102
  // Track removed session
92
103
  if (data.sessionId && onSessionRemovedRef.current) {
93
104
  onSessionRemovedRef.current(data.sessionId);
94
105
  }
95
-
106
+
96
107
  // If the removed sessionId matches the current activeSessionId, immediately clear state
97
108
  if (data.sessionId === currentActiveSessionId) {
98
109
  if (onRemoteSignOutRef.current) {
@@ -100,8 +111,6 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
100
111
  } else {
101
112
  toast.info('You have been signed out remotely.');
102
113
  }
103
- // Use clearSessionState since session was already removed server-side
104
- // Await to ensure storage cleanup completes before continuing
105
114
  try {
106
115
  await clearSessionStateRef.current();
107
116
  } catch (error) {
@@ -110,13 +119,7 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
110
119
  }
111
120
  }
112
121
  } else {
113
- // Otherwise, just refresh the sessions list (with error handling)
114
- refreshSessionsRef.current().catch((error) => {
115
- // Silently handle errors from refresh - they're expected if sessions were removed
116
- if (__DEV__) {
117
- logger.debug('Failed to refresh sessions after session_removed', { component: 'useSessionSocket' }, error as unknown);
118
- }
119
- });
122
+ refreshSessions();
120
123
  }
121
124
  } else if (data.type === 'device_removed') {
122
125
  // Track all removed sessions from this device
@@ -125,16 +128,14 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
125
128
  onSessionRemovedRef.current(sessionId);
126
129
  }
127
130
  }
128
-
131
+
129
132
  // If the removed deviceId matches the current device, immediately clear state
130
- if (data.deviceId && data.deviceId === currentDeviceId) {
133
+ if (data.deviceId && data.deviceId === deviceId) {
131
134
  if (onRemoteSignOutRef.current) {
132
135
  onRemoteSignOutRef.current();
133
136
  } else {
134
137
  toast.info('This device has been removed. You have been signed out.');
135
138
  }
136
- // Use clearSessionState since sessions were already removed server-side
137
- // Await to ensure storage cleanup completes before continuing
138
139
  try {
139
140
  await clearSessionStateRef.current();
140
141
  } catch (error) {
@@ -143,13 +144,7 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
143
144
  }
144
145
  }
145
146
  } else {
146
- // Otherwise, refresh sessions and device list (with error handling)
147
- refreshSessionsRef.current().catch((error) => {
148
- // Silently handle errors from refresh - they're expected if sessions were removed
149
- if (__DEV__) {
150
- logger.debug('Failed to refresh sessions after device_removed', { component: 'useSessionSocket' }, error as unknown);
151
- }
152
- });
147
+ refreshSessions();
153
148
  }
154
149
  } else if (data.type === 'sessions_removed') {
155
150
  // Track all removed sessions
@@ -158,7 +153,7 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
158
153
  onSessionRemovedRef.current(sessionId);
159
154
  }
160
155
  }
161
-
156
+
162
157
  // If the current activeSessionId is in the removed sessionIds list, immediately clear state
163
158
  if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
164
159
  if (onRemoteSignOutRef.current) {
@@ -166,8 +161,6 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
166
161
  } else {
167
162
  toast.info('You have been signed out remotely.');
168
163
  }
169
- // Use clearSessionState since sessions were already removed server-side
170
- // Await to ensure storage cleanup completes before continuing
171
164
  try {
172
165
  await clearSessionStateRef.current();
173
166
  } catch (error) {
@@ -176,23 +169,12 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
176
169
  }
177
170
  }
178
171
  } else {
179
- // Otherwise, refresh sessions list (with error handling)
180
- refreshSessionsRef.current().catch((error) => {
181
- // Silently handle errors from refresh - they're expected if sessions were removed
182
- if (__DEV__) {
183
- logger.debug('Failed to refresh sessions after sessions_removed', { component: 'useSessionSocket' }, error as unknown);
184
- }
185
- });
172
+ refreshSessions();
186
173
  }
187
174
  } else {
188
- // For other event types (e.g., session_created), refresh sessions (with error handling)
189
- refreshSessionsRef.current().catch((error) => {
190
- // Log but don't throw - refresh errors shouldn't break the socket handler
191
- if (__DEV__) {
192
- logger.debug('Failed to refresh sessions after session_update', { component: 'useSessionSocket' }, error as unknown);
193
- }
194
- });
195
-
175
+ // For other event types (e.g., session_created), refresh sessions
176
+ refreshSessions();
177
+
196
178
  // If the current session was logged out (legacy behavior), handle it specially
197
179
  if (data.sessionId === currentActiveSessionId) {
198
180
  if (onRemoteSignOutRef.current) {
@@ -200,8 +182,6 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
200
182
  } else {
201
183
  toast.info('You have been signed out remotely.');
202
184
  }
203
- // Use clearSessionState since session was already removed server-side
204
- // Await to ensure storage cleanup completes before continuing
205
185
  try {
206
186
  await clearSessionStateRef.current();
207
187
  } catch (error) {
@@ -221,4 +201,4 @@ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, ref
221
201
  socketRef.current = null;
222
202
  };
223
203
  }, [userId, baseURL]); // Only depend on userId and baseURL - callbacks are in refs
224
- }
204
+ }
package/src/index.ts CHANGED
@@ -90,6 +90,7 @@ export type {
90
90
 
91
91
  // --- Custom Hooks ---
92
92
  export { useSessionSocket } from './hooks/useSessionSocket';
93
+ export type { UseSessionSocketOptions } from './hooks/useSessionSocket';
93
94
  export { useAssets, setOxyAssetInstance } from './hooks/useAssets';
94
95
  export { useFileDownloadUrl, setOxyFileUrlInstance } from './hooks/useFileDownloadUrl';
95
96
  export { useFollow, useFollowerCounts } from './hooks/useFollow';