Browse Source

v1.0.8: websockets support

CrazyDoctor 6 months ago
parent
commit
c7d0b49e7e

+ 1 - 1
.eslintrc

@@ -1,7 +1,7 @@
 {
 {
   "root": true,
   "root": true,
   "parser": "@typescript-eslint/parser",
   "parser": "@typescript-eslint/parser",
-  "ignorePatterns": ["dist"],
+  "ignorePatterns": ["dist", "test"],
   "parserOptions": {
   "parserOptions": {
     "ecmaVersion": 2020
     "ecmaVersion": 2020
   },
   },

File diff suppressed because it is too large
+ 0 - 0
dist/base/i18n/i18n.js.map


+ 14 - 0
dist/base/websocket/WebSocketHandler.d.ts

@@ -0,0 +1,14 @@
+import { WebSocket as WS } from 'ws';
+declare abstract class WebSocketHandler {
+    static Event: {
+        CONNECTION: string;
+        MESSAGE: string;
+        CLOSE: string;
+        ERROR: string;
+    };
+    abstract onConnect: (ws: WS) => any;
+    abstract onMessage: (message: string) => any;
+    abstract onError: () => any;
+    abstract onClose: (code?: number, reason?: string) => any;
+}
+export { WebSocketHandler };

+ 13 - 0
dist/base/websocket/WebSocketHandler.js

@@ -0,0 +1,13 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.WebSocketHandler = void 0;
+class WebSocketHandler {
+}
+exports.WebSocketHandler = WebSocketHandler;
+WebSocketHandler.Event = {
+    CONNECTION: 'connection',
+    MESSAGE: 'message',
+    CLOSE: 'close',
+    ERROR: 'error'
+};
+//# sourceMappingURL=WebSocketHandler.js.map

+ 1 - 0
dist/base/websocket/WebSocketHandler.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"WebSocketHandler.js","sourceRoot":"","sources":["../../../lib/base/websocket/WebSocketHandler.ts"],"names":[],"mappings":";;;AAEA,MAAe,gBAAgB;;AActB,4CAAgB;AAbV,sBAAK,GAAG;IACrB,UAAU,EAAE,YAAY;IACxB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;CACd,CAAC"}

+ 1 - 0
dist/base/websocket/index.d.ts

@@ -0,0 +1 @@
+export { WebSocketHandler } from './WebSocketHandler';

+ 6 - 0
dist/base/websocket/index.js

@@ -0,0 +1,6 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.WebSocketHandler = void 0;
+var WebSocketHandler_1 = require("./WebSocketHandler");
+Object.defineProperty(exports, "WebSocketHandler", { enumerable: true, get: function () { return WebSocketHandler_1.WebSocketHandler; } });
+//# sourceMappingURL=index.js.map

+ 1 - 0
dist/base/websocket/index.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/base/websocket/index.ts"],"names":[],"mappings":";;;AAAA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA"}

+ 1 - 0
dist/index.d.ts

@@ -1,6 +1,7 @@
 export * from './server';
 export * from './server';
 export * from './base/exceptions';
 export * from './base/exceptions';
 export * from './base/http';
 export * from './base/http';
+export * from './base/websocket';
 export * from './base/util';
 export * from './base/util';
 export * from './base/logger';
 export * from './base/logger';
 export * from './base/i18n';
 export * from './base/i18n';

+ 1 - 0
dist/index.js

@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
 __exportStar(require("./server"), exports);
 __exportStar(require("./server"), exports);
 __exportStar(require("./base/exceptions"), exports);
 __exportStar(require("./base/exceptions"), exports);
 __exportStar(require("./base/http"), exports);
 __exportStar(require("./base/http"), exports);
+__exportStar(require("./base/websocket"), exports);
 __exportStar(require("./base/util"), exports);
 __exportStar(require("./base/util"), exports);
 __exportStar(require("./base/logger"), exports);
 __exportStar(require("./base/logger"), exports);
 __exportStar(require("./base/i18n"), exports);
 __exportStar(require("./base/i18n"), exports);

+ 1 - 1
dist/index.js.map

@@ -1 +1 @@
-{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,oDAAkC;AAClC,8CAA4B;AAC5B,8CAA4B;AAC5B,gDAA8B;AAC9B,8CAA4B"}
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,oDAAkC;AAClC,8CAA4B;AAC5B,mDAAiC;AACjC,8CAA4B;AAC5B,gDAA8B;AAC9B,8CAA4B"}

+ 7 - 0
dist/server/Server.d.ts

@@ -1,9 +1,11 @@
 import { Middleware, Route } from '../base/http';
 import { Middleware, Route } from '../base/http';
 import { Logger, Message } from '../base/logger';
 import { Logger, Message } from '../base/logger';
 import { ServerProperties } from './ServerProperties';
 import { ServerProperties } from './ServerProperties';
+import { WebSocket as WS } from 'ws';
 /** @sealed */
 /** @sealed */
 declare class Server {
 declare class Server {
     private instance;
     private instance;
+    private httpServer;
     private readonly port;
     private readonly port;
     private readonly logger;
     private readonly logger;
     private i18n;
     private i18n;
@@ -11,6 +13,8 @@ declare class Server {
     static readonly defaultMiddlewaresPath = "../middlewares";
     static readonly defaultMiddlewaresPath = "../middlewares";
     static readonly defaultRoutesPath = "../routes";
     static readonly defaultRoutesPath = "../routes";
     private readonly httpHandlers;
     private readonly httpHandlers;
+    private readonly wsHandlers;
+    private readonly wsServers;
     private initialized;
     private initialized;
     private readonly i18nPath?;
     private readonly i18nPath?;
     private readonly middlewaresPath?;
     private readonly middlewaresPath?;
@@ -24,6 +28,9 @@ declare class Server {
     private processHttpHandlers;
     private processHttpHandlers;
     addMiddleware(middleware: Middleware): Server;
     addMiddleware(middleware: Middleware): Server;
     addRoute(route: Route): Server;
     addRoute(route: Route): Server;
+    registerWsServers(): Server;
+    applyWsHandlers(): Server;
+    getWsConnections(url: string): Set<WS> | null;
     logInfo(message: string): void;
     logInfo(message: string): void;
     logError(message: string): void;
     logError(message: string): void;
     logWarn(message: string): void;
     logWarn(message: string): void;

+ 44 - 1
dist/server/Server.js

@@ -44,19 +44,25 @@ const logger_1 = require("../base/logger");
 const i18n_1 = require("../base/i18n");
 const i18n_1 = require("../base/i18n");
 const path_1 = __importDefault(require("path"));
 const path_1 = __importDefault(require("path"));
 const fs_1 = __importDefault(require("fs"));
 const fs_1 = __importDefault(require("fs"));
+const http_2 = __importDefault(require("http"));
+const ws_1 = require("ws");
 const exceptions_3 = require("../base/exceptions");
 const exceptions_3 = require("../base/exceptions");
 const exceptions_4 = require("../base/exceptions");
 const exceptions_4 = require("../base/exceptions");
 const exceptions_5 = require("../base/exceptions");
 const exceptions_5 = require("../base/exceptions");
+const websocket_1 = require("../base/websocket");
 /** @sealed */
 /** @sealed */
 class Server {
 class Server {
     constructor(properties) {
     constructor(properties) {
         this.instance = (0, express_1.default)();
         this.instance = (0, express_1.default)();
+        this.httpServer = http_2.default.createServer(this.instance);
         this.port = properties.port;
         this.port = properties.port;
         this.i18n = i18n_1.i18nLoader.getInstance();
         this.i18n = i18n_1.i18nLoader.getInstance();
         if (properties.locale)
         if (properties.locale)
             this.i18n.setLocale(properties.locale);
             this.i18n.setLocale(properties.locale);
         this.logger = new logger_1.Logger();
         this.logger = new logger_1.Logger();
         this.httpHandlers = {};
         this.httpHandlers = {};
+        this.wsHandlers = properties.wsHandlers || {};
+        this.wsServers = {};
         this.i18nPath = properties.i18nPath;
         this.i18nPath = properties.i18nPath;
         this.middlewaresPath = properties.middlewaresPath;
         this.middlewaresPath = properties.middlewaresPath;
         this.routesPath = properties.routesPath;
         this.routesPath = properties.routesPath;
@@ -87,6 +93,10 @@ class Server {
                 yield this.registerMiddlewares(this.middlewaresPath);
                 yield this.registerMiddlewares(this.middlewaresPath);
             if (this.routesPath)
             if (this.routesPath)
                 yield this.registerRoutes(this.routesPath);
                 yield this.registerRoutes(this.routesPath);
+            if (Object.keys(this.wsHandlers).length > 0) {
+                this.registerWsServers();
+                this.applyWsHandlers();
+            }
         });
         });
     }
     }
     processHttpHandlers() {
     processHttpHandlers() {
@@ -120,6 +130,39 @@ class Server {
                 throw new exceptions_2.IncorrectMethodException();
                 throw new exceptions_2.IncorrectMethodException();
         }
         }
     }
     }
+    registerWsServers() {
+        for (const url of Object.keys(this.wsHandlers)) {
+            const wsServer = this.wsServers[url] = new ws_1.Server({ noServer: true });
+            const wsHandler = this.wsHandlers[url];
+            wsServer.on(websocket_1.WebSocketHandler.Event.CONNECTION, (ws) => {
+                wsHandler.onConnect(ws);
+                ws.on(websocket_1.WebSocketHandler.Event.MESSAGE, wsHandler.onMessage);
+                ws.on(websocket_1.WebSocketHandler.Event.ERROR, wsHandler.onError);
+                ws.on(websocket_1.WebSocketHandler.Event.CLOSE, wsHandler.onClose);
+            });
+        }
+        return this;
+    }
+    applyWsHandlers() {
+        this.httpServer.on('upgrade', (request, socket, head) => {
+            var _a;
+            const url = (_a = request.url) === null || _a === void 0 ? void 0 : _a.split('?')[0];
+            if (url && this.wsHandlers[url] && this.wsServers[url]) {
+                const wsServer = this.wsServers[url];
+                wsServer.handleUpgrade(request, socket, head, (ws) => {
+                    wsServer.emit(websocket_1.WebSocketHandler.Event.CONNECTION, ws, request);
+                });
+            }
+            else {
+                socket.destroy();
+            }
+        });
+        return this;
+    }
+    getWsConnections(url) {
+        const wsServer = this.wsServers[url];
+        return wsServer ? wsServer.clients : null;
+    }
     logInfo(message) {
     logInfo(message) {
         this.logger.getLogger().info(message);
         this.logger.getLogger().info(message);
     }
     }
@@ -198,7 +241,7 @@ class Server {
             if (callback)
             if (callback)
                 callback();
                 callback();
         };
         };
-        this.instance.listen(this.port, cb);
+        this.httpServer.listen(this.port, cb);
     }
     }
 }
 }
 exports.Server = Server;
 exports.Server = Server;

File diff suppressed because it is too large
+ 0 - 0
dist/server/Server.js.map


+ 4 - 0
dist/server/ServerProperties.d.ts

@@ -1,3 +1,4 @@
+import { WebSocketHandler } from '../base/websocket';
 type ServerProperties = {
 type ServerProperties = {
     port: number;
     port: number;
     locale?: string;
     locale?: string;
@@ -6,6 +7,9 @@ type ServerProperties = {
     routesPath?: string;
     routesPath?: string;
     viewEngine?: string;
     viewEngine?: string;
     viewsPath?: string;
     viewsPath?: string;
+    wsHandlers?: {
+        [url: string]: WebSocketHandler;
+    };
     options?: {
     options?: {
         [key: string]: any;
         [key: string]: any;
     };
     };

+ 17 - 0
lib/base/websocket/WebSocketHandler.ts

@@ -0,0 +1,17 @@
+import { WebSocket as WS } from 'ws';
+
+abstract class WebSocketHandler {
+	public static Event = {
+		CONNECTION: 'connection',
+		MESSAGE: 'message',
+		CLOSE: 'close',
+		ERROR: 'error'
+	};
+
+  public abstract onConnect: (ws: WS) => any;
+	public abstract onMessage: (message: string) => any;
+	public abstract onError: () => any;
+	public abstract onClose: (code?: number, reason?: string) => any;
+}
+
+export { WebSocketHandler };

+ 1 - 0
lib/base/websocket/index.ts

@@ -0,0 +1 @@
+export { WebSocketHandler } from './WebSocketHandler';

+ 1 - 0
lib/index.ts

@@ -1,6 +1,7 @@
 export * from './server';
 export * from './server';
 export * from './base/exceptions';
 export * from './base/exceptions';
 export * from './base/http';
 export * from './base/http';
+export * from './base/websocket';
 export * from './base/util';
 export * from './base/util';
 export * from './base/logger';
 export * from './base/logger';
 export * from './base/i18n';
 export * from './base/i18n';

+ 48 - 1
lib/server/Server.ts

@@ -7,13 +7,17 @@ import { ServerProperties } from './ServerProperties';
 import { i18nLoader, $$ } from '../base/i18n';
 import { i18nLoader, $$ } from '../base/i18n';
 import path from 'path';
 import path from 'path';
 import fs from 'fs';
 import fs from 'fs';
+import http from 'http';
+import { Server as WebSocketServer, WebSocket as WS } from 'ws';
 import { InvalidMiddlewareException } from '../base/exceptions';
 import { InvalidMiddlewareException } from '../base/exceptions';
 import { InvalidRouteException } from '../base/exceptions';
 import { InvalidRouteException } from '../base/exceptions';
 import { ServerNotInitializedException } from '../base/exceptions';
 import { ServerNotInitializedException } from '../base/exceptions';
+import { WebSocketHandler } from '../base/websocket';
 
 
 /** @sealed */
 /** @sealed */
 class Server {
 class Server {
 	private instance: Express;
 	private instance: Express;
+	private httpServer: http.Server;
 	private readonly port: number;
 	private readonly port: number;
 	private readonly logger: Logger;
 	private readonly logger: Logger;
 	private i18n: i18nLoader;
 	private i18n: i18nLoader;
@@ -21,6 +25,8 @@ class Server {
 	public static readonly defaultMiddlewaresPath = '../middlewares';
 	public static readonly defaultMiddlewaresPath = '../middlewares';
 	public static readonly defaultRoutesPath = '../routes';
 	public static readonly defaultRoutesPath = '../routes';
 	private readonly httpHandlers: {[key: string]: HttpHandler};
 	private readonly httpHandlers: {[key: string]: HttpHandler};
+	private readonly wsHandlers: {[url: string]: WebSocketHandler};
+	private readonly wsServers: {[url: string]: WebSocketServer};
 	private initialized: boolean;
 	private initialized: boolean;
 
 
 	private readonly i18nPath?: string;
 	private readonly i18nPath?: string;
@@ -32,12 +38,15 @@ class Server {
 
 
 	public constructor(properties: ServerProperties) {
 	public constructor(properties: ServerProperties) {
 		this.instance = express();
 		this.instance = express();
+		this.httpServer = http.createServer(this.instance);
 		this.port = properties.port;
 		this.port = properties.port;
 		this.i18n = i18nLoader.getInstance();
 		this.i18n = i18nLoader.getInstance();
 		if(properties.locale)
 		if(properties.locale)
 			this.i18n.setLocale(properties.locale);
 			this.i18n.setLocale(properties.locale);
 		this.logger = new Logger();
 		this.logger = new Logger();
 		this.httpHandlers = {};
 		this.httpHandlers = {};
+		this.wsHandlers = properties.wsHandlers || {};
+		this.wsServers = {};
 		this.i18nPath = properties.i18nPath;
 		this.i18nPath = properties.i18nPath;
 		this.middlewaresPath = properties.middlewaresPath;
 		this.middlewaresPath = properties.middlewaresPath;
 		this.routesPath = properties.routesPath;
 		this.routesPath = properties.routesPath;
@@ -67,6 +76,10 @@ class Server {
 			await this.registerMiddlewares(this.middlewaresPath);
 			await this.registerMiddlewares(this.middlewaresPath);
 		if(this.routesPath)
 		if(this.routesPath)
 			await this.registerRoutes(this.routesPath);
 			await this.registerRoutes(this.routesPath);
+		if(Object.keys(this.wsHandlers).length > 0) {
+			this.registerWsServers();
+			this.applyWsHandlers();
+		}
 	}
 	}
 
 
 	private processHttpHandlers(): void {
 	private processHttpHandlers(): void {
@@ -104,6 +117,40 @@ class Server {
 		}
 		}
 	}
 	}
 
 
+	public registerWsServers(): Server {
+		for(const url of Object.keys(this.wsHandlers)) {
+			const wsServer = this.wsServers[url] = new WebSocketServer({ noServer: true });
+			const wsHandler = this.wsHandlers[url];
+			wsServer.on(WebSocketHandler.Event.CONNECTION, (ws: WS) => {
+				wsHandler.onConnect(ws);
+				ws.on(WebSocketHandler.Event.MESSAGE, wsHandler.onMessage);
+				ws.on(WebSocketHandler.Event.ERROR, wsHandler.onError);
+				ws.on(WebSocketHandler.Event.CLOSE, wsHandler.onClose);
+			});
+		}
+		return this;
+	}
+
+	public applyWsHandlers(): Server {
+		this.httpServer.on('upgrade', (request, socket, head) => {
+			const url = request.url?.split('?')[0];
+			if(url && this.wsHandlers[url] && this.wsServers[url]) {
+				const wsServer = this.wsServers[url];
+				wsServer.handleUpgrade(request, socket, head, (ws) => {
+					wsServer.emit(WebSocketHandler.Event.CONNECTION, ws, request);
+				});
+			} else {
+				socket.destroy();
+			}
+		});
+		return this;
+	}
+
+	public getWsConnections(url: string): Set<WS> | null {
+		const wsServer = this.wsServers[url];
+		return wsServer ? wsServer.clients : null;
+	}
+
 	public logInfo(message: string): void {
 	public logInfo(message: string): void {
 		this.logger.getLogger().info(message);
 		this.logger.getLogger().info(message);
 	}
 	}
@@ -186,7 +233,7 @@ class Server {
 			this.logInfo($$('org.crazydoctor.expressts.start', { 'port': this.port }));
 			this.logInfo($$('org.crazydoctor.expressts.start', { 'port': this.port }));
 			if(callback) callback();
 			if(callback) callback();
 		};
 		};
-		this.instance.listen(this.port, cb);
+		this.httpServer.listen(this.port, cb);
 	}
 	}
 }
 }
 
 

+ 3 - 0
lib/server/ServerProperties.ts

@@ -1,3 +1,5 @@
+import { WebSocketHandler } from '../base/websocket';
+
 type ServerProperties = {
 type ServerProperties = {
 	port: number,
 	port: number,
 	locale?: string,
 	locale?: string,
@@ -6,6 +8,7 @@ type ServerProperties = {
 	routesPath?: string,
 	routesPath?: string,
   viewEngine?: string,
   viewEngine?: string,
   viewsPath?: string,
   viewsPath?: string,
+  wsHandlers?: {[url: string] : WebSocketHandler},
   options?: {[key: string] : any}
   options?: {[key: string] : any}
 };
 };
 
 

+ 4 - 2
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "org.crazydoctor.expressts",
   "name": "org.crazydoctor.expressts",
-  "version": "1.0.7",
+  "version": "1.0.8",
   "devDependencies": {
   "devDependencies": {
     "@types/express-session": "^1.17.7",
     "@types/express-session": "^1.17.7",
     "@types/node": "^20.4.9",
     "@types/node": "^20.4.9",
@@ -12,6 +12,7 @@
   "dependencies": {
   "dependencies": {
     "@types/express": "^4.17.17",
     "@types/express": "^4.17.17",
     "@types/pino": "^7.0.5",
     "@types/pino": "^7.0.5",
+    "@types/ws": "^8.5.10",
     "express": "^4.18.2",
     "express": "^4.18.2",
     "express-session": "^1.17.3",
     "express-session": "^1.17.3",
     "nodemon": "^3.0.1",
     "nodemon": "^3.0.1",
@@ -19,7 +20,8 @@
     "pino-http": "^8.3.3",
     "pino-http": "^8.3.3",
     "pino-pretty": "^10.2.0",
     "pino-pretty": "^10.2.0",
     "ts-node": "^10.9.1",
     "ts-node": "^10.9.1",
-    "typescript": "^5.1.6"
+    "typescript": "^5.1.6",
+    "ws": "^8.16.0"
   },
   },
   "main": "dist/index.js",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
   "types": "dist/index.d.ts",

+ 49 - 0
test/WebSocketHandlerTest.js

@@ -0,0 +1,49 @@
+const { Server, WebSocketHandler } = require('../dist');
+
+class WSHandler0 extends WebSocketHandler {
+  constructor() {
+    super(...arguments);
+
+    this.onConnect = (ws) => {
+      console.log('ws0 connection:', ws);
+    }
+
+    this.onMessage = (message) => {
+      console.log('ws0 message:', message.toString());
+    }
+
+    this.onError = () => {
+      console.log('ws0 error');
+    }
+
+    this.onClose = (code, reason) => {
+      console.log('ws0 closed', code, reason.toString());
+    }
+  }
+}
+
+class WSHandler1 extends WebSocketHandler {
+  constructor() {
+    super(...arguments);
+
+    this.onConnect = (ws) => {
+      console.log('ws1 connection:', ws);
+    }
+
+    this.onMessage = (message) => {
+      console.log('ws1 message:', message.toString());
+    }
+
+    this.onError = () => {
+      console.log('ws1 error');
+    }
+
+    this.onClose = (code, reason) => {
+      console.log('ws1 closed', code, reason.toString());
+    }
+  }
+}
+
+new Server({ port: 3001, wsHandlers: { '/ws0': new WSHandler0(), '/ws1': new WSHandler1() } }).init().then((server) => {
+  server.start();
+});

+ 1 - 1
tsconfig.json

@@ -9,5 +9,5 @@
     "sourceMap": true
     "sourceMap": true
   },
   },
   "include": ["lib/**/*"],
   "include": ["lib/**/*"],
-  "exclude": ["node_modules"]
+  "exclude": ["node_modules", "test"]
 }
 }

Some files were not shown because too many files changed in this diff