Browse Source

Fix reconnection issues for websocket, as well as connection negotiation

Enzo 3 weeks ago
parent
commit
504390ee6e

+ 0 - 8
Client3f03a18b-13d6-4e99-ba57-1d9119f20a29.json

@@ -1,8 +0,0 @@
-{
-  "uuid": "380dc51e-9dca-4be0-b020-7e45577688f5",
-  "name": "Client3f03a18b-13d6-4e99-ba57-1d9119f20a29",
-  "dateCreated": "2024-10-15T08:15:07.106Z",
-  "transportType": "WEBSOCKET",
-  "eventNotification": null,
-  "instance": null
-}

+ 1 - 1
src/transport/transport.manager.ts

@@ -128,7 +128,7 @@ export class TransportManager {
             next: event => {
                 // new request will be handled. And then manager will here will pick it up from eventNotification and respond accordingly if there's a need for it
                 if (event.event == `Disconnection`) {
-                    console.error(`Client ${event.data?.payload.id} disconnected.`)
+                    console.error(`Client ${event.data?.receiverID?? 'undefined'} disconnected.`)
                     receiver.eventNotification.complete() // no need to listen since it's disconnected. A new one will be established when it reconnects again
                 }
                 if (event.event == `New Message`) {

+ 2 - 2
src/transport/websocket.ts

@@ -1,7 +1,7 @@
 import { Observable, Subject } from "rxjs";
 import { Socket as ClientSocket } from "socket.io-client";
 import { Socket as SocketForConnectedClient } from "socket.io"
-import { handleClientSocketConnection, handleNewSocketClient, startClientSocketConnection, startSocketServer, writeFile } from "../utils/socket.utils";
+import { handleClientSocketConnection, handleNewSocketClient, startClientSocketConnection, startSocketServer } from "../utils/socket.utils";
 import { ITransportReceiving, ITransportTransmitting, ReceiverProfile, TransmitterProfile, TransportEventNotification, TransportMessage, TransportSettings } from "../interface/ITransport.interface";
 
 /* Just code in the context that this websocket service will be handling multiple UI clients. Can think about the server communication at a later time. */
@@ -14,7 +14,7 @@ export class WebsocketTransportService implements ITransportTransmitting, ITrans
         if (setting.profileInfo.port) {
             startSocketServer(setting.profileInfo.port as number).subscribe({
                 next: (connectedClient: SocketForConnectedClient) => {
-                    handleNewSocketClient(connectedClient, this.eventNotification, this.socketReceiverProfile)
+                    handleNewSocketClient(connectedClient, this.socketReceiverProfile).subscribe(this.eventNotification)
                 },
                 error: error => console.error(error),
             })

+ 137 - 104
src/utils/socket.utils.ts

@@ -35,20 +35,15 @@ export function startSocketServer(port: number): Observable<SocketForConnectedCl
 export async function startClientSocketConnection(serverUrl: string): Promise<ClientSocket> {
     return new Promise((resolve, reject) => {
         try {
-            let clientSocket = io(serverUrl)
-            // let clientSocket = io(serverUrl, {
-            //     reconnection: true,              // Enable automatic reconnections
-            //     reconnectionAttempts: 100,       // Retry up to 10 times
-            //     reconnectionDelay: 500,          // Start with a 500ms delay
-            //     reconnectionDelayMax: 10000,     // Delay can grow to a max of 10 seconds
-            //     randomizationFactor: 0.3,
-            // })
-
-            // Listen for a connection event
-            clientSocket.on('connect', () => {
-                console.log('Connected to the server:', clientSocket.id)
-                resolve(clientSocket)
-            });
+            // let clientSocket = io(serverUrl)
+            let clientSocket = io(serverUrl, {
+                reconnection: true,              // Enable automatic reconnections
+                reconnectionAttempts: 1000,       // Retry up to 10 times
+                reconnectionDelay: 500,          // Start with a 500ms delay
+                reconnectionDelayMax: 10000,     // Delay can grow to a max of 10 seconds
+                randomizationFactor: 0.3,
+            })
+            resolve(clientSocket)
         }
         catch (error) {
             reject(error)
@@ -56,39 +51,37 @@ export async function startClientSocketConnection(serverUrl: string): Promise<Cl
     })
 }
 
-// Specifically to write receiver profile information
-export async function writeFile(data: ReceiverProfile, filename: string): Promise<boolean> {
-    return new Promise((resolve, reject) => {
-        // Write JSON data to a file
-        fs.writeFile(`${filename}.json`, JSON.stringify(data, null, 2), (err) => {
-            if (err) {
-                console.error('Error writing file', err);
-                reject(false)
-            } else {
-                console.log('File has been written');
-                resolve(true)
-            }
-        });
-    })
-}
-
 // After establishing connection to the server, set up the credentials, confirm whether or not if there's any credentials, if not ask for one from the server
 export function handleClientSocketConnection(socket: ClientSocket, incomingMessage: Subject<TransportMessage>): Observable<TransportEventNotification> {
     return new Observable((eventNotification: Observer<TransportEventNotification>) => {
+        let clientName!: string
         let buffer: any[] = []
         let receiverProfileInfo!: ReceiverProfile
-        checkOwnClientInfo('client1').then((profile: ReceiverProfile) => {
-            receiverProfileInfo = profile
-            socket.emit('profile', {
-                name: 'Old Client',
-                data: profile
-            })
-        }).catch((error) => {
-            socket.emit('profile', {
-                name: 'New Client',
-                data: null
-            })
-        })
+
+        // Listen for a connection event
+        socket.on('connect', () => {
+            console.log('Connected to the server:', socket.id)
+            if (clientName) {
+                checkOwnClientInfo(clientName).then((profile: ReceiverProfile) => {
+                    receiverProfileInfo = profile
+                    socket.emit('profile', {
+                        name: 'Old Client',
+                        data: profile
+                    })
+                }).catch((error) => {
+                    socket.emit('profile', {
+                        name: 'New Client',
+                        data: null
+                    })
+                })
+            } else {
+                socket.emit('profile', {
+                    name: 'New Client',
+                    data: null
+                })
+            }
+        });
+
         // Listen for messages from the server. Generally here's the responses
         socket.on('message', (msg: any) => {
             console.log(`Websocket Client Transport Receieve Msg`, msg.id)
@@ -97,7 +90,12 @@ export function handleClientSocketConnection(socket: ClientSocket, incomingMessa
                     event: 'New Message',
                     description: 'Received new message',
                     transportType: 'WEBSOCKET',
-                    data: msg
+                    data: {
+                        receiverID: receiverProfileInfo.uuid,
+                        receiverName: receiverProfileInfo.name,
+                        date: new Date(),
+                        payload: msg
+                    }
                 })
                 incomingMessage.next({
                     id: msg.header.MessageID,
@@ -117,30 +115,43 @@ export function handleClientSocketConnection(socket: ClientSocket, incomingMessa
             if (data.name == 'New Profile') {
                 console.log(`Assigned client Name: ${(data.message as ReceiverProfile).name}`)
                 receiverProfileInfo = data.message as ReceiverProfile
-                writeFile(data.message as ReceiverProfile, (data.message as ReceiverProfile).name).then(() => [
+                writeFile(data.message as ReceiverProfile, (data.message as ReceiverProfile).name).then(() => {
+                    clientName = receiverProfileInfo.name
                     // broadcast event to allow retransmission to release buffer
                     eventNotification.next({
                         event: 'Connection',
                         description: 'Profile acquired || updated and stored',
                         transportType: 'WEBSOCKET',
+                        data: {
+                            receiverID: receiverProfileInfo.uuid,
+                            receiverName: receiverProfileInfo.name,
+                            date: new Date(),
+                            payload: receiverProfileInfo
+                        }
                     })
-                ]).catch((error) => { }) // do nothing at the moment. 
+                }).catch((error) => { }) // do nothing at the moment. 
             }
             if (data.name == 'Adjusted Profile') {
                 console.log(`Assigned client Name: ${(data.message as ReceiverProfile).name}`)
                 receiverProfileInfo = data.message as ReceiverProfile
-                writeFile(data.message as ReceiverProfile, (data.message as ReceiverProfile).name).then(() => [
+                writeFile(data.message as ReceiverProfile, (data.message as ReceiverProfile).name).then(() => {
                     // broadcast event to allow retransmission to release buffer
                     eventNotification.next({
                         event: 'Connection',
                         description: 'Profile acquired || updated and stored',
                         transportType: 'WEBSOCKET',
                     })
-                ]).catch((error) => { }) // do nothing at the moment. 
+                }).catch((error) => { }) // do nothing at the moment. 
             }
             if (data.name == 'Error') {
                 console.log(`Server cannot find credentials`, data.message)
                 // logic to request for new credentials
+                setTimeout(() => {
+                    socket.emit('profile', {
+                        name: 'New Client',
+                        data: null
+                    })
+                }, 2000)
             }
         })
 
@@ -158,6 +169,84 @@ export function handleClientSocketConnection(socket: ClientSocket, incomingMessa
     })
 }
 
+// For SERVER Usage: set up socket listeners to start listening for different events
+export function handleNewSocketClient(socket: SocketForConnectedClient, socketReceiverProfile: ReceiverProfile[]): Observable<TransportEventNotification> {
+    return new Observable((event: Observer<TransportEventNotification>) => {
+        console.log(`Setting up listeners for socket:${socket.id}`)
+        // returns the socket client instance 
+        // listen to receiver's initiotion first before assigning 'credentials'
+        socket.on(`profile`, (message: { name: string, data: ReceiverProfile }) => {
+            if (message.name == 'New Client') {
+                let receiverProfile: ReceiverProfile = {
+                    uuid: uuidv4(),
+                    name: `Client${uuidv4()}`,
+                    dateCreated: new Date(),
+                    transportType: `WEBSOCKET`,
+                    eventNotification: new Subject(),
+                    instance: socket
+                }
+                // publish first event notification
+                event.next({
+                    event: 'Connection',
+                    description: 'New Client Connected',
+                    transportType: 'WEBSOCKET',
+                    data: {
+                        receiverID: receiverProfile.uuid,
+                        receiverName: receiverProfile.name,
+                        date: new Date(),
+                        payload: receiverProfile
+                    }
+                })
+                // send to receiver for reference
+                socket.emit('profile', {
+                    name: `New Profile`, message: {
+                        uuid: receiverProfile.uuid,
+                        name: receiverProfile.name,
+                        dateCreated: receiverProfile.dateCreated,
+                        transportType: `WEBSOCKET`,
+                        eventNotification: null,
+                        instance: null // have to put null, otherwise circular reference maximum stack error
+                    }
+                })
+                socketReceiverProfile.push(receiverProfile)
+                startListening(socket, receiverProfile)
+            } else {
+                // update first
+                let receiverProfile: ReceiverProfile | undefined = socketReceiverProfile.find(obj => obj.uuid === message.data.uuid)
+                if (receiverProfile) {
+                    console.log(`Profile ${receiverProfile.uuid} Found`)
+                    receiverProfile.instance = socket
+                    socket.emit('profile', { name: 'Adjusted Profile', message: receiverProfile })
+                    // need to start listening again, because it's assigned a different socket instance this time round
+                    startListening(socket, receiverProfile)
+                } else {
+                    console.log(`Profile Not Found`)
+                    socket.emit('profile', { name: 'Error', message: 'Receiver Profile Not found' })
+                }
+            }
+        })
+    })
+}
+
+
+// Specifically to write receiver profile information
+export async function writeFile(data: ReceiverProfile, filename: string): Promise<boolean> {
+    return new Promise((resolve, reject) => {
+        // Write JSON data to a file
+        fs.writeFile(`${filename}.json`, JSON.stringify(data, null, 2), (err) => {
+            if (err) {
+                console.error('Error writing file', err);
+                reject(false)
+            } else {
+                console.log('File has been written');
+                resolve(true)
+            }
+        });
+    })
+}
+
+
+
 // Check if filename exists. Return profile information if there's any
 export async function checkOwnClientInfo(filename: string): Promise<ReceiverProfile> {
     return new Promise((resolve, reject) => {
@@ -188,62 +277,6 @@ export async function checkOwnClientInfo(filename: string): Promise<ReceiverProf
     })
 }
 
-
-// For SERVER Usage: set up socket listeners to start listening for different events
-export function handleNewSocketClient(socket: SocketForConnectedClient, eventNotification: Subject<TransportEventNotification>, socketReceiverProfile: ReceiverProfile[]): void {
-    console.log(`Setting up listeners for socket:${socket.id}`)
-    // returns the socket client instance 
-    // listen to receiver's initiotion first before assigning 'credentials'
-    socket.on(`profile`, (message: { name: string, data: ReceiverProfile }) => {
-        if (message.name == 'New Client') {
-            let receiverProfile: ReceiverProfile = {
-                uuid: uuidv4(),
-                name: `Client${uuidv4()}`,
-                dateCreated: new Date(),
-                transportType: `WEBSOCKET`,
-                eventNotification: new Subject(),
-                instance: socket
-            }
-            // publish first event notification
-            eventNotification.next({
-                event: 'Connection',
-                description: 'New Client Connected',
-                transportType: 'WEBSOCKET',
-                data: {
-                    receiverID: receiverProfile.uuid,
-                    receiverName: receiverProfile.name,
-                    date: new Date(),
-                    payload: receiverProfile
-                }
-            })
-            // send to receiver for reference
-            socket.emit('profile', {
-                name: `New Profile`, message: {
-                    uuid: receiverProfile.uuid,
-                    name: receiverProfile.name,
-                    dateCreated: receiverProfile.dateCreated,
-                    transportType: `WEBSOCKET`,
-                    eventNotification: null,
-                    instance: null // have to put null, otherwise circular reference maximum stack error
-                }
-            }) 
-            socketReceiverProfile.push(receiverProfile)
-            startListening(socket, receiverProfile)
-        } else {
-            // update first
-            let receiverProfile: ReceiverProfile | undefined = socketReceiverProfile.find(obj => obj.uuid === message.data.uuid)
-            if (receiverProfile) {
-                receiverProfile.instance = socket
-                socket.emit('profile', { name: 'Adjusted Profile', message: receiverProfile })
-                // need to start listening again, because it's assigned a different socket instance this time round
-                startListening(socket, receiverProfile)
-            } else {
-                socket.emit('profile', { name: 'Error', message: 'Receiver Profile Not found' })
-            }
-        }
-    })
-}
-
 export function startListening(socket: SocketForConnectedClient, receiverProfile: ReceiverProfile): void {
     /* Generally, we don't need this unless in the case of being the receiver */
     socket.on('message', (message: any) => {
@@ -273,7 +306,7 @@ export function startListening(socket: SocketForConnectedClient, receiverProfile
         receiverProfile.eventNotification.next(
             {
                 event: 'Disconnection',
-                description: `Existing Client ${socket.id} disonnected`,
+                description: `Existing Client ${receiverProfile.uuid} disonnected`,
                 transportType: `WEBSOCKET`,
                 data: {
                     receiverID: receiverProfile.uuid,