refactor: share realtime socket auth helpers

This commit is contained in:
Saravana Achu Mac 2026-04-04 12:11:43 -07:00
parent e1bb6e790e
commit 8f7d5358aa
4 changed files with 19 additions and 12 deletions

View File

@ -30,7 +30,7 @@ It assumes:
- [x] Backend migrated into `backend/` and passing typecheck, build, test, and backend verification gates
- [x] Web migrated into `web/` with shared runtime, shared kill-switch gate, shared telemetry bootstrap, and normalized backend URL resolution
- [x] Mobile migrated into `mobile/` with product identity, shared runtime bootstrap, launch-time kill-switch gate, transitional Supabase auth, live backend polling plus websocket-backed updates, startup/error telemetry capture, secure session storage with invalidation handling, and explicit degraded/offline status surfacing
- [-] DRY cleanup completed for runtime/config/bootstrap concerns and shared Supabase bootstrap, but not yet for all auth/session internals
- [-] DRY cleanup completed for runtime/config/bootstrap concerns, shared Supabase bootstrap, and shared websocket auth helpers, but not yet for all auth/session internals
- [!] Full common-platform auth replacement remains a follow-up for web and mobile; current implementation uses transitional Supabase-backed auth to stay compatible with the backend's current JWT boundary
## 3. Guiding Rules
@ -261,7 +261,7 @@ Move the web dashboard onto the new repo and onto shared platform bootstrap patt
- [x] Move runtime config to common conventions
- [x] Define product config
- [x] Define API client and websocket client
- [ ] Standardize websocket token propagation
- [x] Standardize websocket token propagation
- [x] Integrate maintenance and kill-switch UX states
- [x] Define shell-level maintenance and kill-switch behavior
- [ ] Classify each current web tab as ship, defer, or redesign

View File

@ -4,6 +4,7 @@ import { io, type Socket } from 'socket.io-client';
import { mobileRuntime } from '@/lib/runtime';
import { mobileTelemetry, trackMobileError } from '@/lib/telemetry';
import { useMobileAuth } from '@/providers/MobileAuthProvider';
import { buildTradingSocketOptions, isUnauthorizedSocketError } from '../../shared/realtime.js';
type HealthSnapshot = {
tradingControl?: {
@ -223,10 +224,7 @@ export function TradingDataProvider({ children }: { children: ReactNode }) {
}));
};
socket = io(tradingSocketUrl, {
transports: ['polling', 'websocket'],
auth: { token: accessToken },
});
socket = io(tradingSocketUrl, buildTradingSocketOptions(accessToken));
socket.on('connect', () => {
setConnected(true);
@ -242,7 +240,7 @@ export function TradingDataProvider({ children }: { children: ReactNode }) {
socket.on('connect_error', (socketError) => {
setError(socketError.message);
trackMobileError('realtime', 'socket_connect_failed', socketError);
if (socketError.message.toLowerCase().includes('unauthorized')) {
if (isUnauthorizedSocketError(socketError.message)) {
void invalidateSession(socketError.message);
}
});

12
shared/realtime.ts Normal file
View File

@ -0,0 +1,12 @@
export function buildTradingSocketOptions(token: string, socketPath?: string) {
return {
transports: ['polling', 'websocket'] as ('polling' | 'websocket')[],
auth: { token },
...(socketPath ? { path: socketPath } : {}),
};
}
export function isUnauthorizedSocketError(message: string) {
const normalizedMessage = message.toLowerCase();
return normalizedMessage.includes('unauthorized') || normalizedMessage.includes('invalid token');
}

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import { buildTradingSocketOptions } from '../../../shared/realtime.js';
import { supabase } from '../lib/supabaseClient';
export interface TradingControlSnapshot {
@ -284,11 +285,7 @@ export const useWebSocket = (url: string) => {
return;
}
const socketOptions = {
transports: ['polling', 'websocket'] as ('polling' | 'websocket')[],
auth: { token },
...(import.meta.env.VITE_SOCKET_PATH ? { path: import.meta.env.VITE_SOCKET_PATH } : {})
};
const socketOptions = buildTradingSocketOptions(token, import.meta.env.VITE_SOCKET_PATH);
newSocket = io(url, socketOptions);