|
@@ -0,0 +1,137 @@
|
|
|
+import EventEmitter from "events";
|
|
|
+import { IncomingMessage } from "http";
|
|
|
+import url from "url";
|
|
|
+import WebSocketLib from "ws";
|
|
|
+import { IConfig } from "../../../config";
|
|
|
+import { Errors, MessageType } from "../../enums";
|
|
|
+import { Client, IClient } from "../../models/client";
|
|
|
+import { IRealm } from "../../models/realm";
|
|
|
+import { MyWebSocket } from "./webSocket";
|
|
|
+
|
|
|
+export interface IWebSocketServer extends EventEmitter {
|
|
|
+ readonly path: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface IAuthParams {
|
|
|
+ id?: string;
|
|
|
+ token?: string;
|
|
|
+ key?: string;
|
|
|
+}
|
|
|
+
|
|
|
+export class WebSocketServer extends EventEmitter implements IWebSocketServer {
|
|
|
+
|
|
|
+ public readonly path: string;
|
|
|
+ private readonly realm: IRealm;
|
|
|
+ private readonly config: IConfig;
|
|
|
+ private readonly webSocketServer: WebSocketLib.Server;
|
|
|
+
|
|
|
+ constructor({ server, realm, config }: { server: any, realm: IRealm, config: IConfig }) {
|
|
|
+ super();
|
|
|
+ this.setMaxListeners(0);
|
|
|
+ this.realm = realm;
|
|
|
+ this.config = config;
|
|
|
+
|
|
|
+ const path = this.config.path;
|
|
|
+ this.path = path + (path[path.length - 1] !== "/" ? "/" : "") + "peerjs";
|
|
|
+
|
|
|
+ this.webSocketServer = new WebSocketLib.Server({ path, server });
|
|
|
+
|
|
|
+ this.webSocketServer.on("connection", (socket: MyWebSocket, req) => this._onSocketConnection(socket, req));
|
|
|
+ this.webSocketServer.on("error", (error: Error) => this._onSocketError(error));
|
|
|
+ }
|
|
|
+
|
|
|
+ private _onSocketConnection(socket: MyWebSocket, req: IncomingMessage): void {
|
|
|
+ const { query = {} } = url.parse(req.url, true);
|
|
|
+
|
|
|
+ const { id, token, key }: IAuthParams = query;
|
|
|
+
|
|
|
+ if (!id || !token || !key) {
|
|
|
+ return this._sendErrorAndClose(socket, Errors.INVALID_WS_PARAMETERS);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (key !== this.config.key) {
|
|
|
+ return this._sendErrorAndClose(socket, Errors.INVALID_KEY);
|
|
|
+ }
|
|
|
+
|
|
|
+ const client = this.realm.getClientById(id);
|
|
|
+
|
|
|
+ if (client) {
|
|
|
+ if (token !== client.getToken()) {
|
|
|
+ // ID-taken, invalid token
|
|
|
+ socket.send(JSON.stringify({
|
|
|
+ type: MessageType.ID_TAKEN,
|
|
|
+ payload: { msg: "ID is taken" }
|
|
|
+ }));
|
|
|
+
|
|
|
+ return socket.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ return this._configureWS(socket, client);
|
|
|
+ }
|
|
|
+
|
|
|
+ this._registerClient({ socket, id, token });
|
|
|
+ }
|
|
|
+
|
|
|
+ private _onSocketError(error: Error): void {
|
|
|
+ // handle error
|
|
|
+ this.emit("error", error);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _registerClient({ socket, id, token }:
|
|
|
+ {
|
|
|
+ socket: MyWebSocket;
|
|
|
+ id: string;
|
|
|
+ token: string;
|
|
|
+ }): void {
|
|
|
+ // Check concurrent limit
|
|
|
+ const clientsCount = this.realm.getClientsIds().length;
|
|
|
+
|
|
|
+ if (clientsCount >= this.config.concurrent_limit) {
|
|
|
+ return this._sendErrorAndClose(socket, Errors.CONNECTION_LIMIT_EXCEED);
|
|
|
+ }
|
|
|
+
|
|
|
+ const newClient: IClient = new Client({ id, token });
|
|
|
+ this.realm.setClient(newClient, id);
|
|
|
+ socket.send(JSON.stringify({ type: MessageType.OPEN }));
|
|
|
+
|
|
|
+ this._configureWS(socket, newClient);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _configureWS(socket: MyWebSocket, client: IClient): void {
|
|
|
+ client.setSocket(socket);
|
|
|
+
|
|
|
+ // Cleanup after a socket closes.
|
|
|
+ socket.on("close", () => {
|
|
|
+ if (client.getSocket() === socket) {
|
|
|
+ this.realm.removeClientById(client.getId());
|
|
|
+ this.emit("close", client);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Handle messages from peers.
|
|
|
+ socket.on("message", (data: WebSocketLib.Data) => {
|
|
|
+ try {
|
|
|
+ const message = JSON.parse(data as string);
|
|
|
+
|
|
|
+ message.src = client.getId();
|
|
|
+
|
|
|
+ this.emit("message", client, message);
|
|
|
+ } catch (e) {
|
|
|
+ this.emit("error", e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.emit("connection", client);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _sendErrorAndClose(socket: MyWebSocket, msg: Errors): void {
|
|
|
+ socket.send(
|
|
|
+ JSON.stringify({
|
|
|
+ type: MessageType.ERROR,
|
|
|
+ payload: { msg }
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ socket.close();
|
|
|
+ }
|
|
|
+}
|