98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
import { HubConnection, HubConnectionBuilder, LogLevel, HttpTransportType } from '@microsoft/signalr';
|
|
import { AppState, AppStateStatus } from 'react-native';
|
|
import { SOCKET_URL } from './config';
|
|
|
|
export interface SocketClient {
|
|
on(event: string, callback: (...args: any[]) => void): void;
|
|
off(event: string, callback?: (...args: any[]) => void): void;
|
|
emit(event: string, ...args: any[]): void;
|
|
disconnect(): void;
|
|
status: string;
|
|
}
|
|
|
|
let connection: HubConnection | null = null;
|
|
let socketClient: SocketClient | null = null;
|
|
let appStateListener: any = null;
|
|
|
|
export function connectSocket(token: string): SocketClient {
|
|
if (connection && (connection.state === 'Connected' || connection.state === 'Connecting')) {
|
|
return socketClient!;
|
|
}
|
|
|
|
connection = new HubConnectionBuilder()
|
|
.withUrl(SOCKET_URL, {
|
|
accessTokenFactory: () => token,
|
|
transport: HttpTransportType.WebSockets | HttpTransportType.LongPolling,
|
|
skipNegotiation: false,
|
|
})
|
|
.withAutomaticReconnect({
|
|
nextRetryDelayInMilliseconds: retryContext => {
|
|
if (retryContext.elapsedMilliseconds < 10000) return 2000;
|
|
if (retryContext.elapsedMilliseconds < 30000) return 10000;
|
|
return 30000;
|
|
}
|
|
})
|
|
.configureLogging(LogLevel.Information)
|
|
.build();
|
|
|
|
socketClient = {
|
|
on: (event: string, callback: (...args: any[]) => void) => {
|
|
connection?.on(event, callback);
|
|
},
|
|
off: (event: string, callback?: (...args: any[]) => void) => {
|
|
if (callback) {
|
|
connection?.off(event, callback);
|
|
} else {
|
|
connection?.off(event);
|
|
}
|
|
},
|
|
emit: (event: string, ...args: any[]) => {
|
|
if (connection?.state === 'Connected') {
|
|
connection.invoke(event, ...args).catch(err => {
|
|
console.error(`SignalR emit error (${event}):`, err);
|
|
});
|
|
} else {
|
|
console.warn(`SignalR emit skipped (${event}): not connected`);
|
|
}
|
|
},
|
|
disconnect: () => {
|
|
if (appStateListener) {
|
|
appStateListener.remove();
|
|
appStateListener = null;
|
|
}
|
|
connection?.stop();
|
|
},
|
|
get status() {
|
|
return connection?.state || 'Disconnected';
|
|
}
|
|
};
|
|
|
|
// Lifecycle handling (AppState)
|
|
appStateListener = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
|
|
if (nextAppState === 'active') {
|
|
if (connection?.state === 'Disconnected') {
|
|
console.log('SignalR: Re-connecting on App Foreground...');
|
|
connection.start().catch(e => console.error('SignalR start error (background to foreground):', e));
|
|
}
|
|
}
|
|
});
|
|
|
|
connection.start()
|
|
.then(() => console.log('SignalR: Connected!'))
|
|
.catch(err => console.error('SignalR: Initial connection error:', err));
|
|
|
|
return socketClient;
|
|
}
|
|
|
|
export function getSocket(): SocketClient | null {
|
|
return socketClient;
|
|
}
|
|
|
|
export function disconnectSocket() {
|
|
if (socketClient) {
|
|
socketClient.disconnect();
|
|
socketClient = null;
|
|
connection = null;
|
|
}
|
|
}
|