|
@@ -7,7 +7,7 @@ import { BehaviorSubject, Observable, Observer, Subject, Subscription } from "rx
|
|
|
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 axios, { AxiosError, AxiosResponse } from 'axios';
|
|
|
import { error } from 'console';
|
|
|
|
|
|
export function startHttpServer(port: number): Observable<ConnectedHttpClient> {
|
|
@@ -30,21 +30,21 @@ 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[], receiverProfileInfo: ConnectedHttpServer | undefined, browserEnv?: boolean,): Promise<ConnectedHttpServer> {
|
|
|
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}`)
|
|
|
+ if (receiverProfileInfo) {
|
|
|
+ console.log(`Is Old profile, reconnecting with server`)
|
|
|
+ checkOwnClientInfo(receiverProfileInfo.id).then((profile: ConnectedHttpServer) => {
|
|
|
+ receiverProfileInfo!.id = profile.id
|
|
|
+ console.log(`jsonfile.`, profile)
|
|
|
+ postAxiosRequest(url + '/profile', { name: 'Old Client', data: profile }).then((profileInfo: { name: string, message: { id: string } }) => {
|
|
|
+ writeFile(profileInfo.message).then((data: any) => {
|
|
|
+ console.log(`Assigned new client Id: ${(data.message as ConnectedHttpServer).id}`)
|
|
|
receiverProfileInfo = data.message as ConnectedHttpServer
|
|
|
- writeFile(data.message as ConnectedHttpServer, (data.message as ConnectedHttpServer).id).then(() => {
|
|
|
+ writeFile(data.message).then(() => {
|
|
|
event.next({
|
|
|
id: uuidv4(),
|
|
|
event: 'Server Connected',
|
|
@@ -62,10 +62,13 @@ export async function initiateClientToServer(url: string, event: Subject<Transpo
|
|
|
resolve(clientObj)
|
|
|
}
|
|
|
})
|
|
|
+ }).catch((error: AxiosError) => {
|
|
|
+ reject(error)
|
|
|
})
|
|
|
}).catch((error) => {
|
|
|
- postAxiosRequest(url + '/profile', { name: 'New Client', data: null }).then((profileInfo: any) => {
|
|
|
- updateProfileAndPublishEvent(clientName, receiverProfileInfo, profileInfo, event, connectedHttpServers).then((receiverProfileInfo) => {
|
|
|
+ console.error(error)
|
|
|
+ postAxiosRequest(url + '/profile', { name: 'New Client', data: null }).then((profileInfo: { name: string, message: any }) => {
|
|
|
+ updateProfileAndPublishEvent((receiverProfileInfo as ConnectedHttpServer), profileInfo, event, connectedHttpServers).then((receiverProfileInfo) => {
|
|
|
resolve(receiverProfileInfo)
|
|
|
})
|
|
|
}).catch((error) => {
|
|
@@ -74,8 +77,8 @@ export async function initiateClientToServer(url: string, event: Subject<Transpo
|
|
|
reject(error)
|
|
|
})
|
|
|
} else {
|
|
|
- postAxiosRequest(url + '/profile', { name: 'New Client', data: null }).then((profileInfo: any) => {
|
|
|
- updateProfileAndPublishEvent(clientName, receiverProfileInfo, profileInfo, event, connectedHttpServers).then((receiverProfileInfo) => {
|
|
|
+ postAxiosRequest(url + '/profile', { name: 'New Client', data: null }).then((profileInfo: { name: string, message: any }) => {
|
|
|
+ updateProfileAndPublishEvent(receiverProfileInfo, profileInfo, event, connectedHttpServers).then((receiverProfileInfo) => {
|
|
|
resolve(receiverProfileInfo)
|
|
|
})
|
|
|
}).catch((error) => {
|
|
@@ -86,97 +89,102 @@ export async function initiateClientToServer(url: string, event: Subject<Transpo
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+
|
|
|
export function handleClientHttpConnection(url: string, server: ConnectedHttpServer): Observable<TransportEvent> {
|
|
|
return new Observable((observer: Observer<TransportEvent>) => {
|
|
|
- server.connectionState.next(`ONLINE`);
|
|
|
-
|
|
|
- const longPoll = async (retryCount = 0) => {
|
|
|
- try {
|
|
|
- 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) {
|
|
|
- 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,
|
|
|
+ server.connectionState.next('ONLINE');
|
|
|
+ let active: boolean = true; // Flag to control polling lifecycle
|
|
|
+
|
|
|
+ const longPoll = async () => {
|
|
|
+ while (active) {
|
|
|
+ try {
|
|
|
+ // Axios request with timeout
|
|
|
+ const response = await axios.get(`${url}/poll`, {
|
|
|
+ timeout: 10000, // 10s timeout
|
|
|
});
|
|
|
- } else if (response.status === 204) {
|
|
|
- console.log('No new messages from the server.');
|
|
|
- } else {
|
|
|
- throw new Error(`Unexpected response status: ${response.status}`);
|
|
|
- }
|
|
|
|
|
|
- retryCount = 0; // Reset retry count on success
|
|
|
- } catch (error: unknown) {
|
|
|
- // Handle unknown errors
|
|
|
- let errorMessage: string;
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = response.data 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,
|
|
|
+ });
|
|
|
+ } else if (response.status === 204) {
|
|
|
+ console.log('No new messages from the server.');
|
|
|
+ } else {
|
|
|
+ handleServerConnectionError(active, observer, server)
|
|
|
+ throw new Error(`Unexpected response status: ${response.status}`);
|
|
|
+ }
|
|
|
+ } catch (error: unknown) {
|
|
|
+ handleServerConnectionError(active, observer, server)
|
|
|
+ // Error handling with server disconnect notification
|
|
|
+ let errorMessage: string;
|
|
|
+
|
|
|
+ if (axios.isAxiosError(error)) {
|
|
|
+ if (error.response) {
|
|
|
+ errorMessage = `Server returned status ${error.response.status}: ${error.response.statusText}`;
|
|
|
+ } else if (error.code === 'ECONNABORTED') {
|
|
|
+ errorMessage = 'Request timed out.';
|
|
|
+ } else {
|
|
|
+ errorMessage = error.message || 'An Axios error occurred.';
|
|
|
+ }
|
|
|
+ } else if (error instanceof Error) {
|
|
|
+ errorMessage = error.message;
|
|
|
+ } else {
|
|
|
+ errorMessage = 'An unknown error occurred during polling.';
|
|
|
+ }
|
|
|
|
|
|
- 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);
|
|
|
+ // observer.error(new Error(errorMessage)); // Notify subscribers of the error
|
|
|
+ break; // Stop polling on error
|
|
|
}
|
|
|
-
|
|
|
- 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
|
|
|
}
|
|
|
-
|
|
|
- setTimeout(() => longPoll(retryCount), 0);
|
|
|
};
|
|
|
|
|
|
longPoll();
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+ // Cleanup logic for unsubscribing
|
|
|
return () => {
|
|
|
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`);
|
|
|
+ observer.complete(); // Notify completion
|
|
|
};
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+function handleServerConnectionError(active: boolean, observer: Observer<TransportEvent>, server: ConnectedHttpServer): void {
|
|
|
+ console.log('Server lost connection');
|
|
|
+ active = false; // Stop polling
|
|
|
+ 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(receiverProfileInfo: ConnectedHttpServer | undefined, 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
|
|
|
+ writeFile(profile.message).then(() => {
|
|
|
event.next({
|
|
|
id: uuidv4(),
|
|
|
event: `New Server`,
|
|
@@ -208,7 +216,7 @@ async function postAxiosRequest(url: string, data: any): Promise<any> {
|
|
|
resolve(response.data)
|
|
|
} catch (error) {
|
|
|
if (axios.isAxiosError(error)) {
|
|
|
- console.error('Axios Error:', error.message);
|
|
|
+ console.error('Axios Error:', error.code);
|
|
|
} else {
|
|
|
console.error('Unexpected Error:', error);
|
|
|
}
|
|
@@ -217,6 +225,7 @@ async function postAxiosRequest(url: string, data: any): Promise<any> {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+
|
|
|
export function handleHttpClient(clientInfo: ConnectedHttpClient, connectedClientHttp: ConnectedHttpClient[]): Observable<TransportEvent> {
|
|
|
return new Observable((event: Observer<TransportEvent>) => {
|
|
|
clientInfo.instance.post('/profile', (req, res) => {
|
|
@@ -376,50 +385,67 @@ export function startListeningAndStreaming(app: Express, client: ConnectedHttpCl
|
|
|
|
|
|
app.get('/poll', (req, res) => {
|
|
|
console.log('Client connected for long polling.');
|
|
|
- eventListener.next({
|
|
|
- id: uuidv4(),
|
|
|
- event: 'Client Connected',
|
|
|
- data: {
|
|
|
- clientId: client.id,
|
|
|
- message: '',
|
|
|
- payload: {
|
|
|
- time: new Date()
|
|
|
- }
|
|
|
- } as EventMessage
|
|
|
- })
|
|
|
+ client.connectionState.next('ONLINE');
|
|
|
+
|
|
|
+ // Flag to track if the response has been sent
|
|
|
+ let responseSent = false;
|
|
|
|
|
|
// Subscribe to the data stream
|
|
|
- const subscription: Subscription = client.responseStream.asObservable().subscribe({
|
|
|
+ const 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
|
|
|
+ if (!responseSent) {
|
|
|
+ console.log(`Sending data to client: ${JSON.stringify(message)}`);
|
|
|
+ res.json({ message }); // Send the data to the client
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ subscription.unsubscribe(); // Unsubscribe to close this request
|
|
|
+ }
|
|
|
},
|
|
|
error: (err) => {
|
|
|
- console.error('Error in data stream:', err);
|
|
|
- res.status(500).send('Internal Server Error');
|
|
|
+ if (!responseSent) {
|
|
|
+ console.error('Error in data stream:', err);
|
|
|
+ res.status(500).send('Internal Server Error');
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ }
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
},
|
|
|
complete: () => {
|
|
|
- console.log('Data stream completed.');
|
|
|
- res.status(204).send(); // No Content
|
|
|
+ if (!responseSent) {
|
|
|
+ console.log('Data stream completed.');
|
|
|
+ res.status(204).send(); // No Content
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ }
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
},
|
|
|
});
|
|
|
|
|
|
+ // Timeout if no data is emitted within a specified duration
|
|
|
+ const timeout = setTimeout(() => {
|
|
|
+ if (!responseSent) {
|
|
|
+ console.log('No data emitted. Sending timeout response.');
|
|
|
+ res.status(204).send(); // No Content
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
+ }
|
|
|
+ }, 15000); // 15 seconds timeout (adjust as needed)
|
|
|
+
|
|
|
// Handle client disconnection
|
|
|
res.on('close', () => {
|
|
|
- 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
|
|
|
+ if (!responseSent) {
|
|
|
+ console.error(`Http Client ${client.id} disconnected`);
|
|
|
+ eventListener.next({
|
|
|
+ id: uuidv4(),
|
|
|
+ event: 'Client Disconnected',
|
|
|
+ data: {
|
|
|
+ clientId: client.id,
|
|
|
+ payload: {
|
|
|
+ time: new Date()
|
|
|
+ }
|
|
|
+ } as EventMessage
|
|
|
+ } as TransportEvent)
|
|
|
+ client.connectionState.next(`OFFLINE`)
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
+ }
|
|
|
+ clearTimeout(timeout); // Clear timeout to avoid unnecessary execution
|
|
|
});
|
|
|
});
|
|
|
|
|
@@ -446,21 +472,20 @@ export async function checkOwnClientInfo(filename?: string): Promise<ConnectedHt
|
|
|
|
|
|
} catch (err) {
|
|
|
// Handle parsing errors or other file-related errors
|
|
|
- console.error("Error reading or parsing file:", err);
|
|
|
- reject('');
|
|
|
+ let errMsg: string = ("Error reading or parsing file: " + err)
|
|
|
+ reject(errMsg);
|
|
|
}
|
|
|
} else {
|
|
|
- console.error("File does not exist");
|
|
|
- reject('');
|
|
|
+ reject("File does not exist");
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// Specifically to write receiver profile information
|
|
|
-export async function writeFile(data: ConnectedHttpServer, filename: string): Promise<boolean> {
|
|
|
+export async function writeFile(data: { id: string }): Promise<boolean> {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
// Write JSON data to a file
|
|
|
- fs.writeFile(`${filename}.json`, JSON.stringify(data, null, 2), (err) => {
|
|
|
+ fs.writeFile(`${data.id}.json`, JSON.stringify(data, null, 2), (err) => {
|
|
|
if (err) {
|
|
|
console.error('Error writing file', err);
|
|
|
reject(false)
|