|
@@ -17,11 +17,6 @@ export function startHttpServer(port: number): Observable<ConnectedHttpClient> {
|
|
// Middleware to parse JSON requests
|
|
// Middleware to parse JSON requests
|
|
app.use(express.json());
|
|
app.use(express.json());
|
|
|
|
|
|
- // Handling a GET request
|
|
|
|
- app.get('/', (req, res) => {
|
|
|
|
- res.send('Hello, World!');
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
app.listen(port, () => {
|
|
app.listen(port, () => {
|
|
console.log(`Server running at http://localhost:${port}`);
|
|
console.log(`Server running at http://localhost:${port}`);
|
|
});
|
|
});
|
|
@@ -36,10 +31,6 @@ export function startHttpServer(port: number): Observable<ConnectedHttpClient> {
|
|
}
|
|
}
|
|
|
|
|
|
export async function initiateClientToServer(url: string, event: Subject<TransportEvent>, connectedHttpServers: ConnectedHttpServer[], browserEnv?: boolean): Promise<ConnectedHttpServer> {
|
|
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) => {
|
|
return new Promise((resolve, reject) => {
|
|
let clientName!: string
|
|
let clientName!: string
|
|
let receiverProfileInfo!: ConnectedHttpServer
|
|
let receiverProfileInfo!: ConnectedHttpServer
|
|
@@ -56,7 +47,7 @@ export async function initiateClientToServer(url: string, event: Subject<Transpo
|
|
writeFile(data.message as ConnectedHttpServer, (data.message as ConnectedHttpServer).id).then(() => {
|
|
writeFile(data.message as ConnectedHttpServer, (data.message as ConnectedHttpServer).id).then(() => {
|
|
event.next({
|
|
event.next({
|
|
id: uuidv4(),
|
|
id: uuidv4(),
|
|
- event: 'Server Reconnected',
|
|
|
|
|
|
+ event: 'Server Connected',
|
|
data: {
|
|
data: {
|
|
clientId: (data.message as ConnectedHttpServer).id,
|
|
clientId: (data.message as ConnectedHttpServer).id,
|
|
message: `Existing Http Channel ${(data.message as ConnectedHttpServer).id} re-established.`
|
|
message: `Existing Http Channel ${(data.message as ConnectedHttpServer).id} re-established.`
|
|
@@ -97,12 +88,18 @@ export async function initiateClientToServer(url: string, event: Subject<Transpo
|
|
|
|
|
|
export function handleClientHttpConnection(url: string, server: ConnectedHttpServer): Observable<TransportEvent> {
|
|
export function handleClientHttpConnection(url: string, server: ConnectedHttpServer): Observable<TransportEvent> {
|
|
return new Observable((observer: Observer<TransportEvent>) => {
|
|
return new Observable((observer: Observer<TransportEvent>) => {
|
|
- // Recursive function to handle long polling
|
|
|
|
- const longPoll = async () => {
|
|
|
|
|
|
+ server.connectionState.next(`ONLINE`);
|
|
|
|
+
|
|
|
|
+ const longPoll = async (retryCount = 0) => {
|
|
try {
|
|
try {
|
|
- const response = await fetch(url); // Make the HTTP request to the server
|
|
|
|
|
|
+ const controller = new AbortController();
|
|
|
|
+ const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
|
|
|
|
+
|
|
|
|
+ const response = await fetch(url + `/poll`, { signal: controller.signal });
|
|
|
|
+ clearTimeout(timeout);
|
|
|
|
+
|
|
if (response.ok) {
|
|
if (response.ok) {
|
|
- const data = await response.json() as WrappedMessage;
|
|
|
|
|
|
+ const data = (await response.json()) as WrappedMessage;
|
|
observer.next({
|
|
observer.next({
|
|
id: uuidv4(),
|
|
id: uuidv4(),
|
|
event: 'New Message',
|
|
event: 'New Message',
|
|
@@ -111,34 +108,69 @@ export function handleClientHttpConnection(url: string, server: ConnectedHttpSer
|
|
dateCreated: new Date(),
|
|
dateCreated: new Date(),
|
|
transport: Transport.Http,
|
|
transport: Transport.Http,
|
|
target: server.id,
|
|
target: server.id,
|
|
- payload: data
|
|
|
|
- } as TransportMessage
|
|
|
|
- }); // Emit the received message to the Observable
|
|
|
|
|
|
+ payload: data,
|
|
|
|
+ } as TransportMessage,
|
|
|
|
+ });
|
|
} else if (response.status === 204) {
|
|
} else if (response.status === 204) {
|
|
- // No Content (keep polling for more updates)
|
|
|
|
console.log('No new messages from the server.');
|
|
console.log('No new messages from the server.');
|
|
} else {
|
|
} else {
|
|
throw new Error(`Unexpected response status: ${response.status}`);
|
|
throw new Error(`Unexpected response status: ${response.status}`);
|
|
}
|
|
}
|
|
- } catch (error) {
|
|
|
|
- observer.error(error); // Notify observer of any errors
|
|
|
|
- return; // Stop polling on errors
|
|
|
|
|
|
+
|
|
|
|
+ retryCount = 0; // Reset retry count on success
|
|
|
|
+ } catch (error: unknown) {
|
|
|
|
+ // Handle unknown errors
|
|
|
|
+ let errorMessage: string;
|
|
|
|
+
|
|
|
|
+ if (error instanceof Error) {
|
|
|
|
+ // Error is of type Error
|
|
|
|
+ errorMessage = error.message;
|
|
|
|
+ console.error(`Polling error: ${errorMessage}`);
|
|
|
|
+ } else if (typeof error === 'string') {
|
|
|
|
+ // Error is a string
|
|
|
|
+ errorMessage = error;
|
|
|
|
+ console.error(`Polling error: ${errorMessage}`);
|
|
|
|
+ } else {
|
|
|
|
+ // Fallback for unknown types
|
|
|
|
+ errorMessage = 'An unknown error occurred during polling.';
|
|
|
|
+ console.error(errorMessage);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ retryCount++;
|
|
|
|
+ if (retryCount > 10) {
|
|
|
|
+ console.error('Max retry attempts reached. Stopping polling.');
|
|
|
|
+ observer.error(new Error(errorMessage)); // Notify observer of final failure
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ console.log(`Retrying (${retryCount})...`);
|
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, retryCount * 1000)); // Exponential backoff
|
|
}
|
|
}
|
|
|
|
|
|
- // Continue polling after processing the response
|
|
|
|
- setTimeout(longPoll, 0); // Optionally add a delay to avoid overwhelming the server
|
|
|
|
|
|
+ setTimeout(() => longPoll(retryCount), 0);
|
|
};
|
|
};
|
|
|
|
|
|
- // Start the long polling
|
|
|
|
longPoll();
|
|
longPoll();
|
|
|
|
|
|
- // Cleanup logic when the observable is unsubscribed
|
|
|
|
return () => {
|
|
return () => {
|
|
console.log('Unsubscribed from the long-polling channel.');
|
|
console.log('Unsubscribed from the long-polling channel.');
|
|
|
|
+ observer.next({
|
|
|
|
+ id: uuidv4(),
|
|
|
|
+ event: 'Server Disconnected',
|
|
|
|
+ data: {
|
|
|
|
+ clientId: server.id,
|
|
|
|
+ message: '',
|
|
|
|
+ payload: {
|
|
|
|
+ time: new Date()
|
|
|
|
+ }
|
|
|
|
+ } as EventMessage
|
|
|
|
+ })
|
|
|
|
+ server.connectionState.next(`OFFLINE`);
|
|
};
|
|
};
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
async function updateProfileAndPublishEvent(clientName: string | undefined, receiverProfileInfo: ConnectedHttpServer, profile: { name: string, message: any }, event: Subject<TransportEvent>, connectedHttpServers: ConnectedHttpServer[]): Promise<ConnectedHttpServer> {
|
|
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) => {
|
|
return new Promise((resolve, reject) => {
|
|
console.log(`Assigned client Name: ${(profile.message as ConnectedHttpServer).id}`)
|
|
console.log(`Assigned client Name: ${(profile.message as ConnectedHttpServer).id}`)
|
|
@@ -249,14 +281,14 @@ function handleProfile(app: Express, data: { name: `Old Client` | `New Client`,
|
|
startListeningAndStreaming(app, clientInstance, event)
|
|
startListeningAndStreaming(app, clientInstance, event)
|
|
event.next({
|
|
event.next({
|
|
id: uuidv4(),
|
|
id: uuidv4(),
|
|
- event: 'Client Reconnected',
|
|
|
|
|
|
+ event: 'Client Connected',
|
|
data: {
|
|
data: {
|
|
clientId: clientInstance.id,
|
|
clientId: clientInstance.id,
|
|
message: `Client ${clientInstance.id} connection re-established`,
|
|
message: `Client ${clientInstance.id} connection re-established`,
|
|
payload: clientInstance
|
|
payload: clientInstance
|
|
} as EventMessage
|
|
} as EventMessage
|
|
})
|
|
})
|
|
-
|
|
|
|
|
|
+
|
|
} else {
|
|
} else {
|
|
console.log(`Profile Not Found`)
|
|
console.log(`Profile Not Found`)
|
|
res.json({ name: 'Error', message: 'Receiver Profile Not found' })
|
|
res.json({ name: 'Error', message: 'Receiver Profile Not found' })
|
|
@@ -326,12 +358,6 @@ export function addClientToDB(entry: ConnectedHttpClient, filePath: string = 'cl
|
|
|
|
|
|
// this is for server usage only
|
|
// this is for server usage only
|
|
export function startListeningAndStreaming(app: Express, client: ConnectedHttpClient, eventListener: Observer<TransportEvent>): void {
|
|
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 */
|
|
/* Generally, we don't need this unless in the case of being the receiver */
|
|
app.post('/message', (req, res) => {
|
|
app.post('/message', (req, res) => {
|
|
eventListener.next({
|
|
eventListener.next({
|
|
@@ -350,7 +376,17 @@ export function startListeningAndStreaming(app: Express, client: ConnectedHttpCl
|
|
|
|
|
|
app.get('/poll', (req, res) => {
|
|
app.get('/poll', (req, res) => {
|
|
console.log('Client connected for long polling.');
|
|
console.log('Client connected for long polling.');
|
|
- client.connectionState.next(`ONLINE`)
|
|
|
|
|
|
+ eventListener.next({
|
|
|
|
+ id: uuidv4(),
|
|
|
|
+ event: 'Client Connected',
|
|
|
|
+ data: {
|
|
|
|
+ clientId: client.id,
|
|
|
|
+ message: '',
|
|
|
|
+ payload: {
|
|
|
|
+ time: new Date()
|
|
|
|
+ }
|
|
|
|
+ } as EventMessage
|
|
|
|
+ })
|
|
|
|
|
|
// Subscribe to the data stream
|
|
// Subscribe to the data stream
|
|
const subscription: Subscription = client.responseStream.asObservable().subscribe({
|
|
const subscription: Subscription = client.responseStream.asObservable().subscribe({
|
|
@@ -371,8 +407,18 @@ export function startListeningAndStreaming(app: Express, client: ConnectedHttpCl
|
|
|
|
|
|
// Handle client disconnection
|
|
// Handle client disconnection
|
|
res.on('close', () => {
|
|
res.on('close', () => {
|
|
- client.connectionState.next('OFFLINE')
|
|
|
|
- console.log('Client disconnected.');
|
|
|
|
|
|
+ console.error(`Http Client ${client.id} disconnected`);
|
|
|
|
+ eventListener.next({
|
|
|
|
+ id: uuidv4(),
|
|
|
|
+ event: 'Client Disconnected',
|
|
|
|
+ data: {
|
|
|
|
+ clientId: client.id,
|
|
|
|
+ message: '',
|
|
|
|
+ payload: {
|
|
|
|
+ time: new Date()
|
|
|
|
+ }
|
|
|
|
+ } as EventMessage
|
|
|
|
+ })
|
|
subscription.unsubscribe(); // Ensure cleanup
|
|
subscription.unsubscribe(); // Ensure cleanup
|
|
});
|
|
});
|
|
});
|
|
});
|