@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
237 lines (225 loc) • 9.18 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useSessionSocket = useSessionSocket;
var _react = require("react");
var _socket = _interopRequireDefault(require("socket.io-client"));
var _sonner = require("../../lib/sonner");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function useSessionSocket({
userId,
activeSessionId,
currentDeviceId,
refreshSessions,
logout,
clearSessionState,
baseURL,
onRemoteSignOut,
onSessionRemoved
}) {
const socketRef = (0, _react.useRef)(null);
const joinedRoomRef = (0, _react.useRef)(null);
// Store callbacks in refs to avoid re-joining when they change
const refreshSessionsRef = (0, _react.useRef)(refreshSessions);
const logoutRef = (0, _react.useRef)(logout);
const clearSessionStateRef = (0, _react.useRef)(clearSessionState);
const onRemoteSignOutRef = (0, _react.useRef)(onRemoteSignOut);
const onSessionRemovedRef = (0, _react.useRef)(onSessionRemoved);
const activeSessionIdRef = (0, _react.useRef)(activeSessionId);
const currentDeviceIdRef = (0, _react.useRef)(currentDeviceId);
// Update refs when callbacks change
(0, _react.useEffect)(() => {
refreshSessionsRef.current = refreshSessions;
logoutRef.current = logout;
clearSessionStateRef.current = clearSessionState;
onRemoteSignOutRef.current = onRemoteSignOut;
onSessionRemovedRef.current = onSessionRemoved;
activeSessionIdRef.current = activeSessionId;
currentDeviceIdRef.current = currentDeviceId;
}, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId]);
(0, _react.useEffect)(() => {
if (!userId || !baseURL) {
// Clean up if userId or baseURL becomes invalid
if (socketRef.current && joinedRoomRef.current) {
socketRef.current.emit('leave', {
userId: joinedRoomRef.current
});
joinedRoomRef.current = null;
}
return;
}
const roomId = `user:${userId}`;
// Only create socket if it doesn't exist
if (!socketRef.current) {
socketRef.current = (0, _socket.default)(baseURL, {
transports: ['websocket']
});
}
const socket = socketRef.current;
// Only join if we haven't already joined this room
if (joinedRoomRef.current !== roomId) {
// Leave previous room if switching users
if (joinedRoomRef.current) {
socket.emit('leave', {
userId: joinedRoomRef.current
});
}
socket.emit('join', {
userId: roomId
});
joinedRoomRef.current = roomId;
if (__DEV__) {
console.log('Emitting join for room:', roomId);
}
}
// Set up event handlers (only once per socket instance)
const handleConnect = () => {
if (__DEV__) {
console.log('Socket connected:', socket.id);
}
};
const handleSessionUpdate = async data => {
if (__DEV__) {
console.log('Received session_update:', data);
}
const currentActiveSessionId = activeSessionIdRef.current;
const currentDeviceId = currentDeviceIdRef.current;
// Handle different event types
if (data.type === 'session_removed') {
// Track removed session
if (data.sessionId && onSessionRemovedRef.current) {
onSessionRemovedRef.current(data.sessionId);
}
// If the removed sessionId matches the current activeSessionId, immediately clear state
if (data.sessionId === currentActiveSessionId) {
if (onRemoteSignOutRef.current) {
onRemoteSignOutRef.current();
} else {
_sonner.toast.info('You have been signed out remotely.');
}
// Use clearSessionState since session was already removed server-side
// Await to ensure storage cleanup completes before continuing
try {
await clearSessionStateRef.current();
} catch (error) {
if (__DEV__) {
console.error('Failed to clear session state after session_removed:', error);
}
}
} else {
// Otherwise, just refresh the sessions list (with error handling)
refreshSessionsRef.current().catch(error => {
// Silently handle errors from refresh - they're expected if sessions were removed
if (__DEV__) {
console.debug('Failed to refresh sessions after session_removed:', error);
}
});
}
} else if (data.type === 'device_removed') {
// Track all removed sessions from this device
if (data.sessionIds && onSessionRemovedRef.current) {
for (const sessionId of data.sessionIds) {
onSessionRemovedRef.current(sessionId);
}
}
// If the removed deviceId matches the current device, immediately clear state
if (data.deviceId && data.deviceId === currentDeviceId) {
if (onRemoteSignOutRef.current) {
onRemoteSignOutRef.current();
} else {
_sonner.toast.info('This device has been removed. You have been signed out.');
}
// Use clearSessionState since sessions were already removed server-side
// Await to ensure storage cleanup completes before continuing
try {
await clearSessionStateRef.current();
} catch (error) {
if (__DEV__) {
console.error('Failed to clear session state after device_removed:', error);
}
}
} else {
// Otherwise, refresh sessions and device list (with error handling)
refreshSessionsRef.current().catch(error => {
// Silently handle errors from refresh - they're expected if sessions were removed
if (__DEV__) {
console.debug('Failed to refresh sessions after device_removed:', error);
}
});
}
} else if (data.type === 'sessions_removed') {
// Track all removed sessions
if (data.sessionIds && onSessionRemovedRef.current) {
for (const sessionId of data.sessionIds) {
onSessionRemovedRef.current(sessionId);
}
}
// If the current activeSessionId is in the removed sessionIds list, immediately clear state
if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
if (onRemoteSignOutRef.current) {
onRemoteSignOutRef.current();
} else {
_sonner.toast.info('You have been signed out remotely.');
}
// Use clearSessionState since sessions were already removed server-side
// Await to ensure storage cleanup completes before continuing
try {
await clearSessionStateRef.current();
} catch (error) {
if (__DEV__) {
console.error('Failed to clear session state after sessions_removed:', error);
}
}
} else {
// Otherwise, refresh sessions list (with error handling)
refreshSessionsRef.current().catch(error => {
// Silently handle errors from refresh - they're expected if sessions were removed
if (__DEV__) {
console.debug('Failed to refresh sessions after sessions_removed:', error);
}
});
}
} else {
// For other event types (e.g., session_created), refresh sessions (with error handling)
refreshSessionsRef.current().catch(error => {
// Log but don't throw - refresh errors shouldn't break the socket handler
if (__DEV__) {
console.debug('Failed to refresh sessions after session_update:', error);
}
});
// If the current session was logged out (legacy behavior), handle it specially
if (data.sessionId === currentActiveSessionId) {
if (onRemoteSignOutRef.current) {
onRemoteSignOutRef.current();
} else {
_sonner.toast.info('You have been signed out remotely.');
}
// Use clearSessionState since session was already removed server-side
// Await to ensure storage cleanup completes before continuing
try {
await clearSessionStateRef.current();
} catch (error) {
if (__DEV__) {
console.error('Failed to clear session state after session_update:', error);
}
}
}
}
};
socket.on('connect', handleConnect);
socket.on('session_update', handleSessionUpdate);
return () => {
socket.off('connect', handleConnect);
socket.off('session_update', handleSessionUpdate);
// Only leave on unmount if we're still in this room
if (joinedRoomRef.current === roomId) {
socket.emit('leave', {
userId: roomId
});
joinedRoomRef.current = null;
}
};
}, [userId, baseURL]); // Only depend on userId and baseURL - callbacks are in refs
}
//# sourceMappingURL=useSessionSocket.js.map