170 lines
5.1 KiB
TypeScript
170 lines
5.1 KiB
TypeScript
import { Response } from "express";
|
|
import { logger } from "../app";
|
|
import { ISeverity, NotificationType, PublicNotificationType } from "../models/notifications/notification.interface";
|
|
import { addNotification } from "../models/notifications/notification.model";
|
|
import { IPhone } from "../models/phone/phone.interface";
|
|
import { IUser } from "../models/user/user.interface";
|
|
import { User } from "../models/user/user.model";
|
|
import { randomString } from "./crypto";
|
|
|
|
/**
|
|
* This class stores one specific client.
|
|
*/
|
|
export class Client {
|
|
id: string;
|
|
userId: string;
|
|
stream: Response;
|
|
|
|
constructor(stream: Response, userId: string) {
|
|
this.id = randomString(16);
|
|
this.userId = userId;
|
|
this.stream = stream;
|
|
}
|
|
|
|
send(type: NotificationType, data: any) {
|
|
this.stream.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
}
|
|
|
|
async getUser() {
|
|
return await User.findById(this.userId);
|
|
}
|
|
}
|
|
|
|
export class Clients {
|
|
private clients: Client[];
|
|
|
|
constructor(clients: Client[]) {
|
|
this.clients = clients;
|
|
}
|
|
|
|
getClientsByUser(userId: string) {
|
|
const userClients = [];
|
|
for (let i = 0; i < this.clients.length; i++) {
|
|
if (this.clients[i].userId === userId) {
|
|
userClients.push(this.clients[i]);
|
|
}
|
|
}
|
|
|
|
return userClients;
|
|
}
|
|
|
|
closeAllClientsByUser(userId: string) {
|
|
this.getClientsByUser(userId).forEach(client => {
|
|
client.stream.end();
|
|
});
|
|
}
|
|
|
|
addClient(client: Client) {
|
|
this.clients.push(client);
|
|
|
|
return client;
|
|
}
|
|
|
|
getClients() {
|
|
return this.clients;
|
|
}
|
|
}
|
|
|
|
export class EventManager {
|
|
constructor() {
|
|
setInterval(() => {
|
|
this.broadcast('info', { message: "Test" });
|
|
}, 2000);
|
|
}
|
|
|
|
// This map stores a open data stream and it's associated room.
|
|
private clients: Clients = new Clients([]);
|
|
|
|
private addClient(stream: Response, userId: string) {
|
|
this.clients.addClient(new Client(stream, userId));
|
|
}
|
|
|
|
/**
|
|
* Add a client to a specific room
|
|
* @param room Used as an id for the specific room
|
|
* @param stream A open connection to the user
|
|
*/
|
|
async join(userId: string, stream: Response) {
|
|
if (stream.req == undefined) {
|
|
stream.send(500);
|
|
return;
|
|
}
|
|
|
|
// Check user
|
|
const user = await User.findById(userId);
|
|
if (user === null) {
|
|
stream.send(401);
|
|
return;
|
|
}
|
|
|
|
// Make sure to keep the connection open
|
|
stream.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
'Connection': 'keep-alive'
|
|
});
|
|
|
|
this.addClient(stream, userId);
|
|
logger.debug(`Client ${stream.req.hostname} of user ${user.name} joined.`);
|
|
}
|
|
|
|
/**
|
|
* Push a new event into a specific room
|
|
* @param event Type of the event
|
|
* @param data Content of the event
|
|
* @param selector Room to push in. If empty then it will be a public broadcast to anyone.
|
|
*/
|
|
push(type: NotificationType, data: any, user: IUser, severity = ISeverity.INFO) {
|
|
let clients = this.clients.getClientsByUser(user.id);
|
|
if (clients === undefined) return;
|
|
|
|
/* Manage notifications */
|
|
if (type != 'beat' && user !== undefined) {
|
|
if (type == 'phone_alive' || type == 'phone_dead') {
|
|
addNotification(type, severity, ((data as IPhone)._id), user);
|
|
}
|
|
}
|
|
|
|
data = { type, severity, ...data };
|
|
|
|
clients.forEach((client) => {
|
|
client.stream.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
});
|
|
|
|
if (user === undefined) {
|
|
logger.debug(`Broadcasted event ${type} to all users (${clients.length} clients affected)`);
|
|
} else {
|
|
logger.debug(`Broadcasted event ${type} to user ${user.id} (${clients.length} clients affected)`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Very much like push() but it will send this message to **every connected client!**
|
|
*/
|
|
broadcast(type: PublicNotificationType, data: any) {
|
|
this.clients.getClients().forEach(async client => {
|
|
console.log(`Send ${JSON.stringify(data)} (of type ${type}) to a client of user ${(await client.getUser())?.name}`);
|
|
client.stream.write(`event: message\ndata: ${JSON.stringify(data)}\n\n`);
|
|
});
|
|
|
|
logger.debug(`Broadcasted event ${type} to all users (${this.clients.getClients().length} clients affected)`);
|
|
}
|
|
|
|
/**
|
|
* End the communication with a specific client.
|
|
*/
|
|
end(stream: Response, userId: string) {
|
|
stream.end();
|
|
|
|
logger.debug(`End connection with ${stream.req?.hostname} (user: ${userId})`);
|
|
}
|
|
|
|
static buildEventTypeName(type: EventType, user: IUser) {
|
|
return `${type}-${user}`;
|
|
}
|
|
}
|
|
|
|
export type EventType =
|
|
| 'tracker' // Receive just the gps location of a specific user.
|
|
| 'user' // Receive user updates.
|
|
| 'all'; // Receive all above events.
|