ソースを参照

faulty http service. TO be fixed

Dr-Swopt 1 ヶ月 前
コミット
e0a6f0863c

+ 2 - 1
.env

@@ -1,4 +1,5 @@
 ;Transport = "Websocket, Http"
 ;PORT = 3000, 3001
-Transport = "Websocket"
+# Transport = "Websocket"
+Transport = "Http"
 PORT = 3000

+ 1 - 1
dist/config/config.json

@@ -1,5 +1,5 @@
 {
     "connection": {
-        "transmitter": "http://localhost:3001/"
+        "transmitter": "http://localhost:3000/"
     }
 }

+ 9 - 24
doc/explanation.txt

@@ -1,17 +1,3 @@
-As of 14/11/2024 <Since there's no slot for meeting for the rest of the week>
-Things to consider:
-i) Multi Client test. <Based on chin, this is out of my scope, but will do anyways.>
--Make sure it can work with multiple clients. And yes, more proxy will need to be prepared to simulate that.
-ii) Request Response simulation. Make sure the receiver can make request and acquire response (Not referring to request Response adapter. Just use the existing two channel)
--Enable the response generator aspect to simulate such cases. Also, there is a need for a counter so to speak, so that the message
-received by the receiver matches. 
--Also, need to consider also seqences once the above is tested. Because retransmission does help in wrapping. Either the retransmission do the wrapping or this message interface 
-do the wrapping. <TBD> {Just a thought, the wrapping message format can be implemented across, since transmission and adapter level don't really care?? Need to check again}
-iii) Http Transport service
--This one will take some time, because need to emulate it to mimic bidirectional streaming, so there's a need to emulate logical channel 
-iv) Think about requestresponse transmission and it's associated adapter's implementation
--Do this when I have too much time to waste
-
 Discussion Points: (Do these first)
 i) Explore multiple traffic concept
 -To make use of different ports as well as multi ISP to stream data. Eg: External Netword card.
@@ -19,24 +5,23 @@ i) Explore multiple traffic concept
 ii) Move transport service instantiation to adapterManager
 ExtraNotes: In some cases, servers can only be transmitting. Although this program allows for dual roles if there's a need for me.
 
-For 21/11/2024 (Thursday):
-i) Test multi client.  <DONE>
--This is probelmatic. Observation: One client, but the all the transmission stop, but it shouldn't be.
-ii) Do include in the list of discussion for the message ordering mechanism
+For 25/11/2024 Monday
+i) Do include in the list of discussion for the message ordering mechanism
 - Currently the way it is working is not favorable. In the mean time, do think up suggestion to improve the ordering.
 - Just to acquire boss's input. Doesn't have to challenge him or anything
-iii) Start doing some R&D.
--details down there
-iv) Also, enhancements to cater for UI side to use this mesasge transmission interface.  [MAJOR OVERHAUL]
-- That means when instantiating message transmission, the browser environment must be specified to use socket client instead of starting a server. 
-- Need to redo all the test again, (request-response emulation && multi Client Test)
+ii) Http Service Prep
+-Need to emulate logical channel somehow.
+iii) TCP Service Prep
+-Try this out, so that I can proceed with multi port and metric performance
+-Adapter Manager need to be intelligent enough to instantiate necessary components. So there will be a need to also check if a port is busy or in used.
+
 
 Things to do:
 - Connection Manager to manage different transport options. Default using websocket, but will also consider fail detection on each transport and decide on adapters swap
 - Also need to cater for browser environment. Right the now, the default behaviour is that one would assume to instantiate a socket server, instead of client. 
 Need to cater for those cases too.
 
-Target for week:
+Target for week: (Same for Week4)
 i) R&D for multi channel data traversal.
 -That means utilizing multiple TCP ports or network cards or transport services.
 -To be Prep via documents for discussion 

+ 142 - 15
package-lock.json

@@ -9,8 +9,9 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
+        "axios": "^1.7.7",
         "dotenv": "^16.4.5",
-        "express": "^4.21.0",
+        "express": "^4.21.1",
         "rxjs": "^7.8.1",
         "socket.io": "^4.8.0",
         "socket.io-client": "^4.8.0",
@@ -168,6 +169,23 @@
       "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
       "license": "MIT"
     },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.7.7",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
+      "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "node_modules/base64id": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -244,6 +262,18 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/content-disposition": {
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -327,6 +357,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -453,9 +492,9 @@
       }
     },
     "node_modules/express": {
-      "version": "4.21.0",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
-      "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
+      "version": "4.21.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
+      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
       "license": "MIT",
       "dependencies": {
         "accepts": "~1.3.8",
@@ -463,7 +502,7 @@
         "body-parser": "1.20.3",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
-        "cookie": "0.6.0",
+        "cookie": "0.7.1",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -495,9 +534,9 @@
       }
     },
     "node_modules/express/node_modules/cookie": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
-      "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+      "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
       "license": "MIT",
       "engines": {
         "node": ">= 0.6"
@@ -551,6 +590,40 @@
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
       "license": "MIT"
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+      "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -836,6 +909,12 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
     "node_modules/qs": {
       "version": "6.13.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -1331,6 +1410,21 @@
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
       "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
     },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "axios": {
+      "version": "1.7.7",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
+      "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+      "requires": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "base64id": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -1387,6 +1481,14 @@
         "set-function-length": "^1.2.1"
       }
     },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
     "content-disposition": {
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -1437,6 +1539,11 @@
         "gopd": "^1.0.1"
       }
     },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+    },
     "depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1520,16 +1627,16 @@
       "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
     },
     "express": {
-      "version": "4.21.0",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
-      "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
+      "version": "4.21.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
+      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
       "requires": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
         "body-parser": "1.20.3",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
-        "cookie": "0.6.0",
+        "cookie": "0.7.1",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -1558,9 +1665,9 @@
       },
       "dependencies": {
         "cookie": {
-          "version": "0.6.0",
-          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
-          "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
+          "version": "0.7.1",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+          "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="
         },
         "debug": {
           "version": "2.6.9",
@@ -1606,6 +1713,21 @@
         }
       }
     },
+    "follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
+    },
+    "form-data": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+      "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      }
+    },
     "forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1777,6 +1899,11 @@
         "ipaddr.js": "1.9.1"
       }
     },
+    "proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
     "qs": {
       "version": "6.13.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",

+ 2 - 1
package.json

@@ -21,8 +21,9 @@
     "typescript": "^5.6.3"
   },
   "dependencies": {
+    "axios": "^1.7.7",
     "dotenv": "^16.4.5",
-    "express": "^4.21.0",
+    "express": "^4.21.1",
     "rxjs": "^7.8.1",
     "socket.io": "^4.8.0",
     "socket.io-client": "^4.8.0",

+ 1 - 1
src/config/config.json

@@ -1,5 +1,5 @@
 {
     "connection": {
-        "transmitter": "http://localhost:3001/"
+        "transmitter": "http://localhost:3000/"
     }
 }

+ 7 - 3
src/connector/connector.manager.ts

@@ -52,12 +52,12 @@ console.log(config);export class ConnectionManager implements ConnectionManagerI
 
 
     // Server to be set up as well as acquiring client information if needed. Like in the case for grpc and socket. Http not requ`ired.
-    private setUpTransportService(transportSet: TransportSet, event: Subject<TransportEvent>, browserEnv?: boolean): void {
+    private setUpTransportService(transportSet: TransportSet, event: Subject<TransportEvent>, isClient?: boolean): void {
         this.instantiateTransportService(transportSet.transport, event).then((transportService: TransportService) => {
             this.transportServiceArray.push(transportService)
             if (transportService instanceof WebsocketTransportService) {
                 console.log(`Just Double Checking... this is websocket`)
-                if(browserEnv) {
+                if(isClient) {
                     // please note this is subject to change depending on the UI environemnt. Angular has their own built in function to read json file based on Swopt-UI
                     transportService.startClient(config.connection.transmitter) 
                 } else {
@@ -65,8 +65,12 @@ console.log(config);export class ConnectionManager implements ConnectionManagerI
                 }
             } else if (transportService instanceof HttpTransportService) {
                 console.log(`Just Double Checking... this is http`)
-                transportService.startServer(transportSet.port);
                 // Additional Http-specific setup if needed.
+                if(isClient) {
+                    transportService.startClient(`http://localhost:3000`)
+                } else {
+                    transportService.startServer(transportSet.port)
+                }
             }
         }).catch((error) => { throw new Error(error) })
     }

+ 2 - 1
src/interface/connector.interface.ts

@@ -58,7 +58,8 @@ export enum AdaptorTransmissionRole {
 export enum Transport {
     Websocket = `Websocket`,
     Grpc = `Grpc`,
-    Http = `Http`
+    Http = `Http`,
+    TCP = 'TCP'
 }
 
 // TO be used for transmission at the trasport level

+ 62 - 10
src/transport/http.ts

@@ -1,26 +1,78 @@
+import { Express } from 'express';
 import { Observable, Subject } from "rxjs";
-import { Transport, TransportEvent, TransportMessage, TransportService } from "../interface/connector.interface";
+import { ClientObject, Transport, TransportEvent, TransportMessage, TransportService } from "../interface/connector.interface";
+import { v4 as uuidv4 } from 'uuid'
+import config from '../config/config.json';
+import { handleClientHttpConnection, handleHttpClient, initiateClientToServer, startHttpServer } from "../utils/http.utils";
+import { WrappedMessage } from '../utils/message.ordering';
+import { error } from 'console';
 
 export class HttpTransportService implements TransportService {
+    private baseUrl!: string;
+    private info: Transport = Transport.Http
+    private connectedHttpServer: ConnectedHttpServer[] = [] // to allow the possibility of having to communicate with multiple servers as a client
+    private connectedHttpClients: ConnectedHttpClient[] = [] // to keep track of the all the clients that are connected
     transportEvent!: Subject<TransportEvent>
 
     constructor(event: Subject<TransportEvent>) {
+        this.baseUrl = config.connection.transmitter
         this.transportEvent = event
     }
 
-    getInfo(): Transport {
-        throw new Error("Method not implemented.");
+    public getInfo(): Transport {
+        return this.info
     }
 
-    emit(message: TransportMessage): void {
-        throw new Error("Method not implemented.");
+    public emit(message: TransportMessage): void {
+        let clientObj: ConnectedHttpClient | undefined = this.connectedHttpClients.find(obj => obj.id == message.target)
+        let serverObj: ConnectedHttpServer | undefined = this.connectedHttpServer.find(obj => obj.id === message.target)
+
+        // for server usage
+        if (clientObj && clientObj.connectionState.getValue() == 'ONLINE') {
+            clientObj.instance.emit(`message`, message.payload)
+        }
+        // for client usage
+        if (serverObj && serverObj.connectionState.getValue() == 'ONLINE') {
+            fetch(`${this.baseUrl}/message`, {
+                method: 'POST',
+                headers: { 'Content-Type': 'application/json' },
+                body: JSON.stringify(message),
+            }).catch((error) => console.error('HTTP emit error:', error));
+        }
+
     }
 
-    subscribe(): Observable<TransportEvent> {
-        throw new Error("Method not implemented.");
+    public subscribe(): Observable<TransportEvent> {
+        return this.transportEvent.asObservable();
     }
-   
-    startServer(port: number): void {
-        // start HTTP server
+
+    public startServer(port: number): void {
+        startHttpServer(port).subscribe({
+            next: (client: ConnectedHttpClient) => {
+                handleHttpClient(client, this.connectedHttpClients).subscribe({
+                    next: event => this.transportEvent.next(event),
+                    error: error => console.error(error),
+                    complete: () => (`Client ${client.id} disconnected...`)
+                })
+            },
+            error: error => console.error(error),
+            complete: () => console.log(`...`)
+        })
+    }
+
+    public startClient(url: string): void {
+        initiateClientToServer(url, this.transportEvent, this.connectedHttpServer).then((connectedHttpServer: ConnectedHttpServer) => {
+            handleClientHttpConnection(url, connectedHttpServer).subscribe(this.transportEvent)
+        }).catch((error) => console.error(`HttpTransport ERROR:`, error))
     }
+
+}
+
+
+export interface ConnectedHttpClient extends ClientObject {
+    instance: Express
+    responseStream: Subject<WrappedMessage>
+}
+
+export interface ConnectedHttpServer extends ClientObject {
 }

+ 1 - 2
src/transport/websocket.ts

@@ -2,7 +2,7 @@ import { BehaviorSubject, filter, Observable, Subject } from "rxjs";
 import { Socket as ClientSocket } from 'socket.io-client'
 import { Socket as SocketForConnectedClient } from "socket.io"
 import { handleClientSocketConnection, handleNewSocketClient, startClientSocketConnection, startSocketServer } from "../utils/socket.utils";
-import { ClientObject, ConnectionState, Info, Transport, TransportEvent, TransportMessage, TransportService } from "../interface/connector.interface";
+import { ClientObject, Transport, TransportEvent, TransportMessage, TransportService } from "../interface/connector.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. */
 export class WebsocketTransportService implements TransportService {
@@ -22,7 +22,6 @@ export class WebsocketTransportService implements TransportService {
         // logic here
         startSocketServer(port).subscribe({
             next: (connectedClient: SocketForConnectedClient) => {
-                console.log(`WebsocketTransport Server Started...`)
                 handleNewSocketClient(connectedClient, this.connectedClientSocket).subscribe({
                     next: event => this.transportEvent.next(event),
                     error: error => console.error(error),

+ 433 - 0
src/utils/http.utils.ts

@@ -0,0 +1,433 @@
+import express, { Response } from 'express';
+import * as fs from 'fs'
+import { Express } from 'express';
+import { v4 as uuidv4 } from 'uuid'
+import { ConnectedHttpClient, ConnectedHttpServer } from "../transport/http";
+import { BehaviorSubject, Observable, Observer, Subject, Subscription } from "rxjs";
+import { ConnectionState, Transport, TransportEvent, TransportMessage } from '../interface/connector.interface';
+import { EventMessage, FisMessage } from '../interface/transport.interface';
+import { WrappedMessage } from './message.ordering';
+import axios, { AxiosResponse } from 'axios';
+import { error } from 'console';
+
+export function startHttpServer(port: number): Observable<ConnectedHttpClient> {
+    return new Observable((observer: Observer<ConnectedHttpClient>) => {
+        let app: Express = express();
+
+        // Middleware to parse JSON requests
+        app.use(express.json());
+
+        // Handling a GET request
+        app.get('/', (req, res) => {
+            res.send('Hello, World!');
+        });
+
+        // Handling a POST request
+        app.post('/data', (req, res) => {
+            const { name, age } = req.body;
+            res.json({ message: `Received data: ${name}, ${age}` });
+        });
+
+        app.listen(port, () => {
+            console.log(`Server running at http://localhost:${port}`);
+        });
+
+        observer.next({
+            id: uuidv4(),
+            dateCreated: new Date(),
+            connectionState: new BehaviorSubject<ConnectionState>('ONLINE'),
+            instance: app
+        } as ConnectedHttpClient)
+    })
+}
+
+export async function initiateClientToServer(url: string, event: Subject<TransportEvent>, connectedHttpServers: ConnectedHttpServer[], browserEnv?: boolean): Promise<ConnectedHttpServer> {
+    /* Here's what needs to be done. Set up profile first before attempting to long poll.
+    Essentially, this is setting to receive responses from the server. Need to have additional checkign 
+    to see if hte server's connection status. With regards to sending request, well just utilize
+    the fetch method as written in the service. */
+    return new Promise((resolve, reject) => {
+        let clientName!: string
+        let receiverProfileInfo!: ConnectedHttpServer
+        if (browserEnv) {
+            // logic here for using browser fetch
+        } else { // axios methods
+            if (clientName) {
+                checkOwnClientInfo(clientName).then((profile: ConnectedHttpServer) => {
+                    receiverProfileInfo = profile
+                    postAxiosRequest(url + 'profile', { name: 'Old Client', data: profile }).then((profileInfo: any) => {
+                        writeFile(profileInfo.message, (profileInfo.message as ConnectedHttpServer).id).then((data: any) => {
+                            console.log(`Assigned client Name: ${(data.message as ConnectedHttpServer).id}`)
+                            receiverProfileInfo = data.message as ConnectedHttpServer
+                            writeFile(data.message as ConnectedHttpServer, (data.message as ConnectedHttpServer).id).then(() => {
+                                event.next({
+                                    id: uuidv4(),
+                                    event: 'Server Reconnected',
+                                    data: {
+                                        clientId: (data.message as ConnectedHttpServer).id,
+                                        message: `Existing Http Channel ${(data.message as ConnectedHttpServer).id} re-established.`
+                                    } as EventMessage
+                                })
+                            })
+                            // Update Http instance record
+                            let clientObj: ConnectedHttpServer | undefined = connectedHttpServers.find(obj => obj.id === (data.message as ConnectedHttpServer).id)
+                            if (clientObj) {
+                                receiverProfileInfo.connectionState = clientObj.connectionState
+                                clientObj.connectionState.next('ONLINE')
+                                resolve(clientObj)
+                            }
+                        })
+                    })
+                }).catch((error) => {
+                    postAxiosRequest(url + 'profile', { name: 'New Client', data: null }).then((profileInfo: any) => {
+                        updateProfileAndPublishEvent(clientName, receiverProfileInfo, profileInfo, event, connectedHttpServers).then((receiverProfileInfo) => {
+                            resolve(receiverProfileInfo)
+                        })
+                    }).catch((error) => {
+                        reject(error)
+                    })
+                    reject(error)
+                })
+            } else {
+                postAxiosRequest(url + 'profile', { name: 'New Client', data: null }).then((profileInfo: any) => {
+                    updateProfileAndPublishEvent(clientName, receiverProfileInfo, profileInfo, event, connectedHttpServers).then((receiverProfileInfo) => {
+                        resolve(receiverProfileInfo)
+                    })
+                }).catch((error) => {
+                    reject(error)
+                })
+            }
+        }
+    })
+}
+
+export function handleClientHttpConnection(url: string, server: ConnectedHttpServer): Observable<TransportEvent> {
+    return new Observable((observer: Observer<TransportEvent>) => {
+        // Recursive function to handle long polling
+        const longPoll = async () => {
+            try {
+                const response = await fetch(url); // Make the HTTP request to the server
+                if (response.ok) {
+                    const data = await response.json() as WrappedMessage;
+                    observer.next({
+                        id: uuidv4(),
+                        event: 'New Message',
+                        data: {
+                            id: uuidv4(),
+                            dateCreated: new Date(),
+                            transport: Transport.Http,
+                            target: server.id,
+                            payload: data
+                        } as TransportMessage
+                    }); // Emit the received message to the Observable
+                } else if (response.status === 204) {
+                    // No Content (keep polling for more updates)
+                    console.log('No new messages from the server.');
+                } else {
+                    throw new Error(`Unexpected response status: ${response.status}`);
+                }
+            } catch (error) {
+                observer.error(error); // Notify observer of any errors
+                return; // Stop polling on errors
+            }
+
+            // Continue polling after processing the response
+            setTimeout(longPoll, 0); // Optionally add a delay to avoid overwhelming the server
+        };
+
+        // Start the long polling
+        longPoll();
+
+        // Cleanup logic when the observable is unsubscribed
+        return () => {
+            console.log('Unsubscribed from the long-polling channel.');
+        };
+    });
+}
+
+async function updateProfileAndPublishEvent(clientName: string | undefined, receiverProfileInfo: ConnectedHttpServer, profile: { name: string, message: any }, event: Subject<TransportEvent>, connectedHttpServers: ConnectedHttpServer[]): Promise<ConnectedHttpServer> {
+    return new Promise((resolve, reject) => {
+        console.log(`Assigned client Name: ${(profile.message as ConnectedHttpServer).id}`)
+        receiverProfileInfo = profile.message as ConnectedHttpServer
+        writeFile(profile.message, (profile.message as ConnectedHttpServer).id).then(() => {
+            clientName = receiverProfileInfo.id
+            event.next({
+                id: uuidv4(),
+                event: `New Server`,
+                data: {
+                    clientId: (profile.message as ConnectedHttpServer).id,
+                    message: `New Http Channel ${(profile.message as ConnectedHttpServer).id} established.`
+                } as EventMessage
+            })
+        }).catch((error) => {
+            reject(error)
+        })
+
+        // Update http instance record
+        receiverProfileInfo = {
+            id: (profile.message as ConnectedHttpServer).id,
+            dateCreated: new Date(),
+            connectionState: new BehaviorSubject<ConnectionState>(`ONLINE`)
+        }
+        connectedHttpServers.push(receiverProfileInfo)
+        resolve(receiverProfileInfo)
+    })
+}
+
+async function postAxiosRequest(url: string, data: any): Promise<any> {
+    return new Promise(async (resolve, reject) => {
+        try {
+            const response: AxiosResponse<any> = await axios.post(url, data);
+            console.log('Response:', response.data);
+            resolve(response.data)
+        } catch (error) {
+            if (axios.isAxiosError(error)) {
+                console.error('Axios Error:', error.message);
+            } else {
+                console.error('Unexpected Error:', error);
+            }
+            reject(error)
+        }
+    })
+}
+
+export function handleHttpClient(clientInfo: ConnectedHttpClient, connectedClientHttp: ConnectedHttpClient[]): Observable<TransportEvent> {
+    return new Observable((event: Observer<TransportEvent>) => {
+        clientInfo.instance.post('/profile', (req, res) => {
+            // Client will declare this first before attempting to poll for response channel
+            handleProfile(clientInfo.instance, req.body, res, event, connectedClientHttp)
+        });
+    })
+}
+
+export function handleProfile(app: Express, data: { name: `Old Client` | `New Client`, message: any }, res: Response, event: Observer<TransportEvent>, connectedClientHttp: ConnectedHttpClient[]): void {
+    if (data.name == `New Client`) {
+        let clientInstance: ConnectedHttpClient = {
+            id: uuidv4(), // client should only be assigned at this level. And is passed around for reference pointing
+            dateCreated: new Date(),
+            instance: app,
+            connectionState: new BehaviorSubject<ConnectionState>(`OFFLINE`), // for now it's offline because it needs to establish the long polling first
+            responseStream: new Subject<WrappedMessage>()
+        }
+
+        // send to receiver for reference
+        res.json({
+            name: `New Client`, message: { id: clientInstance.id }
+        })
+        // publish first event notification
+        event.next({
+            id: uuidv4(),
+            event: `New Client`,
+            data: {
+                clientId: clientInstance.id,
+                message: `New Http Client Connected. Adapter ID assigned: ${clientInstance.id}`,
+                payload: clientInstance
+            } as EventMessage
+        })
+        // Update connected clientInstance info to adapter
+        connectedClientHttp.push(clientInstance)
+        addClientToDB(clientInstance)
+        startListeningAndStreaming(app, clientInstance, event)
+    } else {
+        // update first
+        let clientInstance: ConnectedHttpClient | undefined
+        if (connectedClientHttp.length > 0) {
+            clientInstance = connectedClientHttp.find(obj => obj.id === data.message.id)
+            handleFoundClient(clientInstance)
+        } else {
+            // for the case server itself got shit down or something
+            checkIfClientExists(data.message.id).then((client: ConnectedHttpClient) => {
+                clientInstance = client
+                handleFoundClient(clientInstance)
+            }).catch(error => console.error(error))
+        }
+        function handleFoundClient(clientInstance: ConnectedHttpClient | undefined): void {
+            if (clientInstance) {
+                console.log(`Http Client ${clientInstance.id} Found`)
+                res.json({ name: 'Adjusted Profile', message: { id: clientInstance.id } })
+                // replace socket instance since the previous has been terminated
+                clientInstance.instance = app
+                // some explanation here. For the case where the server reads from the DB, no need to terminate subject, since all instances would be destroyed alongside the server shut down. This case is specificd only when there's a need to read from local file
+                if (!clientInstance.connectionState) {
+                    clientInstance.connectionState = new BehaviorSubject<ConnectionState>(`OFFLINE`)
+                }
+                // need to start listening again, because it's assigned a different socket instance this time round
+                startListeningAndStreaming(app, clientInstance, event)
+                event.next({
+                    id: uuidv4(),
+                    event: 'Client Reconnected',
+                    data: {
+                        clientId: clientInstance.id,
+                        message: `Client ${clientInstance.id} connection re-established`,
+                        payload: clientInstance
+                    } as EventMessage
+                })
+                
+            } else {
+                console.log(`Profile Not Found`)
+                res.json({ name: 'Error', message: 'Receiver Profile Not found' })
+            }
+        }
+    }
+
+
+}
+
+export async function checkIfClientExists(id: string, filePath: string = 'clients.json'): Promise<ConnectedHttpClient> {
+    return new Promise((resolve, reject) => {
+        try {
+            // Check if the file exists
+            if (!fs.existsSync(filePath)) {
+                console.log("File does not exist.");
+                reject('File does not exist');
+            }
+
+            // Read and parse the data
+            const fileContent = fs.readFileSync(filePath, 'utf-8');
+            const data: any[] = JSON.parse(fileContent);
+
+            // Check if an object with the given id exists
+            let obj = data.find(entry => entry.id === id);
+
+            if (obj) {
+                console.log(`Client with ID ${id} exists.`);
+            } else {
+                console.log(`Client with ID ${id} does not exist.`);
+            }
+
+            resolve(obj);
+        } catch (error) {
+            console.error('Error reading the file:', error);
+            reject(`Error reading the file`)
+        }
+    })
+}
+
+/* For Internal Usage only. Temporary serve as a way for server to keep track of clients. To be replaced in the future with better alternatives. */
+export function addClientToDB(entry: ConnectedHttpClient, filePath: string = 'clients.json'): void {
+    try {
+        let data: ConnectedHttpClient[] = [];
+
+        // Check if the file exists and load existing data
+        if (fs.existsSync(filePath)) {
+            const fileContent = fs.readFileSync(filePath, 'utf-8');
+            data = JSON.parse(fileContent);
+        }
+
+        // Append the new object to the array
+        data.push({
+            id: entry.id,
+            dateCreated: entry.dateCreated,
+            connectionState: null,
+            instance: null
+        } as unknown as ConnectedHttpClient);
+
+        // Write the updated array back to the file
+        fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
+        console.log(`Entry added successfully.`);
+    } catch (error) {
+        console.error('Error writing to file:', error);
+    }
+}
+
+// this is for server usage only
+export function startListeningAndStreaming(app: Express, client: ConnectedHttpClient, eventListener: Observer<TransportEvent>): void {
+    // sample
+    app.post('/data', (req, res) => {
+        const { name, age } = req.body;
+        res.json({ message: `Received data: ${name}, ${age}` });
+    });
+
+    /* Generally, we don't need this unless in the case of being the receiver */
+    app.post('/message', (req, res) => {
+        eventListener.next({
+            id: uuidv4(),
+            event: 'New Message',
+            data: {
+                id: uuidv4(),
+                dateCreated: new Date(),
+                transport: Transport.Http,
+                target: client.id, // this ref to be associated with the client/channel
+                payload: req.body
+            } as TransportMessage
+        })
+        res.json(`Received ${(req.body as FisMessage)?.header?.messageID ?? `Undefined`}`)
+    })
+
+    app.get('/poll', (req, res) => {
+        console.log('Client connected for long polling.');
+
+        // Subscribe to the data stream
+        const subscription: Subscription = client.responseStream.asObservable().subscribe({
+            next: (message: WrappedMessage) => {
+                console.log(`Sending data to client: ${message}`);
+                res.json({ message }); // Send the data to the client
+                subscription.unsubscribe(); // End the current request
+            },
+            error: (err) => {
+                console.error('Error in data stream:', err);
+                res.status(500).send('Internal Server Error');
+            },
+            complete: () => {
+                console.log('Data stream completed.');
+                res.status(204).send(); // No Content
+            },
+        });
+
+        // Handle client disconnection
+        res.on('close', () => {
+            client.connectionState.next('OFFLINE')
+            console.log('Client disconnected.');
+            subscription.unsubscribe(); // Ensure cleanup
+        });
+    });
+
+}
+
+
+// Check if filename exists. Return profile information if there's any
+export async function checkOwnClientInfo(filename?: string): Promise<ConnectedHttpServer> {
+    return new Promise((resolve, reject) => {
+        // Check if the file exists
+        if (fs.existsSync(`${filename}.json`)) {
+            try {
+                // Read the file contents
+                const fileData = fs.readFileSync(`${filename}.json`, 'utf8');
+
+                // If the file is empty, return an error
+                if (fileData.trim() === "") {
+                    throw new Error("File is empty");
+                }
+
+                // Parse and return the data if present
+                const jsonData = JSON.parse(fileData);
+                resolve(jsonData)
+
+            } catch (err) {
+                // Handle parsing errors or other file-related errors
+                console.error("Error reading or parsing file:", err);
+                reject('');
+            }
+        } else {
+            console.error("File does not exist");
+            reject('');
+        }
+    })
+}
+
+// Specifically to write receiver profile information
+export async function writeFile(data: ConnectedHttpServer, 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)
+            }
+        });
+    })
+}
+

+ 4 - 5
src/utils/socket.utils.ts

@@ -126,7 +126,6 @@ export function handleClientSocketConnection(socket: ClientSocket, serversConnec
                             message: `New Websocket Channel ${(data.message as ConnectedServerSocket).id} established.`
                         } as EventMessage
                     })
-
                 }).catch((error) => { }) // do nothing at the moment. 
                 // Update websocket instance record
                 receiverProfileInfo = {
@@ -213,7 +212,7 @@ export function handleNewSocketClient(socket: SocketForConnectedClient, connecte
                     event: `New Client`,
                     data: {
                         clientId: clientInstance.id,
-                        message: `New Client Connected. Adapter ID assigned: ${clientInstance.id}`,
+                        message: `New Socket Client Connected. Adapter ID assigned: ${clientInstance.id}`,
                         payload: clientInstance
                     } as EventMessage
                 })
@@ -226,15 +225,15 @@ export function handleNewSocketClient(socket: SocketForConnectedClient, connecte
                 let clientInstance: ConnectedClientSocket | undefined
                 if (connectedClientSocket.length > 0) {
                     clientInstance = connectedClientSocket.find(obj => obj.id === message.data.id)
-                    temp(clientInstance)
+                    handleFoundClient(clientInstance)
                 } else {
                     // for the case server itself got shit down or something
                     checkIfClientExists(message.data.id).then((client: ConnectedClientSocket) => {
                         clientInstance = client
-                        temp(clientInstance)
+                        handleFoundClient(clientInstance)
                     }).catch(error => console.error(error))
                 }
-                function temp(clientInstance: ConnectedClientSocket | undefined) {
+                function handleFoundClient(clientInstance: ConnectedClientSocket | undefined) {
                     if (clientInstance) {
                         console.log(`Socket Client ${clientInstance.id} Found`)
                         socket.emit('profile', { name: 'Adjusted Profile', message: { id: clientInstance.id } })