// services/socketService.ts import { PAYMENT_SOCKET_BASE_URL, payments, STORAGE_KEYS, } from "@/constants/config"; import AsyncStorage from "@react-native-async-storage/async-storage"; import io, { Socket } from "socket.io-client"; class SocketService { private socket: Socket | null = null; private static instance: SocketService; private constructor() {} public static getInstance(): SocketService { if (!SocketService.instance) { SocketService.instance = new SocketService(); } return SocketService.instance; } public async connect(): Promise { try { const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); if (!token) { throw new Error("No auth token found"); } this.socket = io(PAYMENT_SOCKET_BASE_URL, { transports: ["websocket"], autoConnect: true, reconnection: true, reconnectionDelay: 1000, reconnectionAttempts: 5, timeout: 20000, extraHeaders: { Authorization: `Bearer ${token}`, }, }); return new Promise((resolve, reject) => { this.socket!.on("connect", () => { console.log("Socket connected:", this.socket?.id); resolve(this.socket!); }); this.socket!.on("connect_error", (error) => { console.error("Socket connection error:", error); reject(error); }); this.socket!.on("disconnect", (reason) => { console.log("Socket disconnected:", reason); }); }); } catch (error) { console.error("Failed to connect socket:", error); return Promise.reject(error); } } public registerTransaction(transactionId: string): Promise { return new Promise((resolve, reject) => { const socket = this.socket; // Save reference to socket here if (!socket || !socket.connected) { reject(new Error("Socket not connected")); return; } const timeoutSecs = payments.SOCKET_CONNECTION_TIMEOUT_IN_SECS * 1000; const timeoutId = setTimeout(() => { socket.off("registration-ack", onRegistrationAck); reject(new Error("Timeout: No registration-ack received")); }, timeoutSecs); const onRegistrationAck = (data: any) => { if (data.transactionId === transactionId) { clearTimeout(timeoutId); socket.off("registration-ack", onRegistrationAck); if (data.success) { resolve(); } else { reject(new Error("Registration failed")); } } }; //register the transaction with the specific 'transactionId' socket.emit(payments.REGISTER_TRANSACTION_EMIT_EVENT_NAME, { transactionId, }); socket.on( payments.REGISTER_TRANSACTION_LISTEN_EVENT_NAME, onRegistrationAck ); }); } //payment confirmation is received on 'register-transaction' eventName public onPaymentConfirmation(callback: (data: any) => void): void { if (!this.socket) { console.error("Socket not connected"); return; } this.socket.on(payments.PAYMENT_CONFIRMATION_EVENT_NAME, callback); } public offPaymentConfirmation(): void { if (this.socket) { this.socket.off(payments.PAYMENT_CONFIRMATION_EVENT_NAME); } } public disconnect(): void { if (this.socket) { this.socket.disconnect(); this.socket = null; } } public isConnected(): boolean { return this.socket?.connected || false; } public getSocket(): Socket | null { return this.socket; } } export default SocketService.getInstance();