2023-11-07 07:26:45 -08:00
|
|
|
import { EventEmitter } from 'events';
|
|
|
|
import { assert, jsonStringify } from 'n8n-workflow';
|
2023-02-10 06:02:47 -08:00
|
|
|
import type { IPushDataType } from '@/Interfaces';
|
2023-10-25 07:35:22 -07:00
|
|
|
import { Logger } from '@/Logger';
|
2023-11-07 07:26:45 -08:00
|
|
|
import type { User } from '@/databases/entities/User';
|
2023-02-10 06:02:47 -08:00
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
/**
|
|
|
|
* Abstract class for two-way push communication.
|
|
|
|
* Keeps track of user sessions and enables sending messages.
|
|
|
|
*
|
|
|
|
* @emits message when a message is received from a client
|
|
|
|
*/
|
|
|
|
export abstract class AbstractPush<T> extends EventEmitter {
|
2023-02-10 06:02:47 -08:00
|
|
|
protected connections: Record<string, T> = {};
|
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
protected userIdBySessionId: Record<string, string> = {};
|
|
|
|
|
2023-02-10 06:02:47 -08:00
|
|
|
protected abstract close(connection: T): void;
|
|
|
|
protected abstract sendToOne(connection: T, data: string): void;
|
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
constructor(protected readonly logger: Logger) {
|
|
|
|
super();
|
|
|
|
}
|
2023-10-25 07:35:22 -07:00
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
protected add(sessionId: string, userId: User['id'], connection: T): void {
|
|
|
|
const { connections, userIdBySessionId: userIdsBySessionId } = this;
|
2023-10-25 07:35:22 -07:00
|
|
|
this.logger.debug('Add editor-UI session', { sessionId });
|
2023-02-10 06:02:47 -08:00
|
|
|
|
|
|
|
const existingConnection = connections[sessionId];
|
|
|
|
if (existingConnection) {
|
|
|
|
// Make sure to remove existing connection with the same id
|
|
|
|
this.close(existingConnection);
|
|
|
|
}
|
|
|
|
|
|
|
|
connections[sessionId] = connection;
|
2023-11-07 07:26:45 -08:00
|
|
|
userIdsBySessionId[sessionId] = userId;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected onMessageReceived(sessionId: string, msg: unknown): void {
|
|
|
|
this.logger.debug('Received message from editor-UI', { sessionId, msg });
|
|
|
|
const userId = this.userIdBySessionId[sessionId];
|
|
|
|
this.emit('message', {
|
|
|
|
sessionId,
|
|
|
|
userId,
|
|
|
|
msg,
|
|
|
|
});
|
2023-02-10 06:02:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
protected remove(sessionId?: string): void {
|
|
|
|
if (sessionId !== undefined) {
|
2023-10-25 07:35:22 -07:00
|
|
|
this.logger.debug('Remove editor-UI session', { sessionId });
|
2023-02-10 06:02:47 -08:00
|
|
|
delete this.connections[sessionId];
|
2023-11-07 07:26:45 -08:00
|
|
|
delete this.userIdBySessionId[sessionId];
|
2023-02-10 06:02:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
private sendToSessions<D>(type: IPushDataType, data: D, sessionIds: string[]) {
|
|
|
|
this.logger.debug(`Send data of type "${type}" to editor-UI`, {
|
|
|
|
dataType: type,
|
|
|
|
sessionIds: sessionIds.join(', '),
|
|
|
|
});
|
|
|
|
|
|
|
|
const sendData = jsonStringify({ type, data }, { replaceCircularRefs: true });
|
|
|
|
|
|
|
|
for (const sessionId of sessionIds) {
|
|
|
|
const connection = this.connections[sessionId];
|
|
|
|
assert(connection);
|
|
|
|
this.sendToOne(connection, sendData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
broadcast<D>(type: IPushDataType, data?: D) {
|
|
|
|
this.sendToSessions(type, data, Object.keys(this.connections));
|
|
|
|
}
|
|
|
|
|
|
|
|
send<D>(type: IPushDataType, data: D, sessionId: string) {
|
2023-02-10 06:02:47 -08:00
|
|
|
const { connections } = this;
|
2023-11-07 07:26:45 -08:00
|
|
|
if (connections[sessionId] === undefined) {
|
2023-10-25 07:35:22 -07:00
|
|
|
this.logger.error(`The session "${sessionId}" is not registered.`, { sessionId });
|
2023-02-10 06:02:47 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
this.sendToSessions(type, data, [sessionId]);
|
|
|
|
}
|
2023-02-10 06:02:47 -08:00
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
/**
|
|
|
|
* Sends the given data to given users' connections
|
|
|
|
*/
|
|
|
|
sendToUsers<D>(type: IPushDataType, data: D, userIds: Array<User['id']>) {
|
|
|
|
const { connections } = this;
|
|
|
|
const userSessionIds = Object.keys(connections).filter((sessionId) =>
|
|
|
|
userIds.includes(this.userIdBySessionId[sessionId]),
|
|
|
|
);
|
2023-02-10 06:02:47 -08:00
|
|
|
|
2023-11-07 07:26:45 -08:00
|
|
|
this.sendToSessions(type, data, userSessionIds);
|
2023-02-10 06:02:47 -08:00
|
|
|
}
|
|
|
|
}
|