├── .eslintrc.js ├── .gitignore ├── BUMP_VERSION.md ├── CHANGELOG.md ├── GENERATING_DOCS.md ├── LICENSE ├── README.md ├── example ├── example-browser-client.ts ├── example-browser-server.ts ├── example-browser.html ├── example-readme.ts ├── example-tcp.ts └── example-ws.ts ├── index.ts ├── package-lock.json ├── package.json ├── src ├── ByteSize.ts ├── Client.ts ├── Server.ts ├── SocketFactory.ts ├── TCPClient.ts ├── TCPServer.ts ├── VirtualClient.ts ├── VirtualServer.ts ├── WSClient.ts ├── WSServer.ts ├── WrappedClient.ts ├── index.ts └── types.ts ├── test ├── Client.spec.ts ├── Server.spec.ts ├── SocketFactory.spec.ts ├── TCPClient.spec.ts ├── TCPServer.spec.ts ├── VirtualClient.spec.ts ├── WSClient.spec.ts ├── WSServer.spec.ts ├── cert │ └── generate_self_signed_cert.sh ├── connection.spec.ts ├── connectionTCP.spec.ts ├── connectionTCPTLS.spec.ts └── connectionTLS.spec.ts ├── testy.json └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | parser: "@typescript-eslint/parser", 7 | plugins: [ 8 | "@typescript-eslint" 9 | ], 10 | rules: { 11 | "no-cond-assign": "off", 12 | "@typescript-eslint/no-empty-interface": "off", 13 | "@typescript-eslint/no-explicit-any": "off", 14 | "@typescript-eslint/ban-ts-comment": "off", 15 | "@typescript-eslint/no-inferrable-types": "off", 16 | "@typescript-eslint/no-empty-function": "off", 17 | "no-constant-condition": "off", 18 | "@typescript-eslint/no-non-null-assertion": "off" 19 | }, 20 | root: true, 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | docs 4 | -------------------------------------------------------------------------------- /BUMP_VERSION.md: -------------------------------------------------------------------------------- 1 | # How to bump version 2 | 3 | 1. Update CHANGELOG.md 4 | 2. Update package.json to new version 5 | 3. Run `npm i` to update package-lock.json 6 | 4. Run `npm run build` to build 7 | 5. Commit changes 8 | 6. Tag commit with new version 9 | 7. Push to remote 10 | 8. Publish to the npm registry 11 | 9. Done 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG: pocket-sockets 2 | 3 | ## [4.0.0] - 20240307 4 | Improve event handling. 5 | Bug fix. 6 | 7 | ## [3.0.0] - 20240220 8 | Add text-mode options to socket. 9 | Add WrappedClient to allow for more complex clients. 10 | Add helper functions getSocket and isWebSocket to ClientInterface. 11 | Block possibility to listen to port 0. 12 | Add linting. 13 | Add ClientInterface.isClosed() and Server.isClosed(). 14 | Add ClientInterface.init() to allow for other more complex clients. 15 | Add WrappedClient to allow wrapping a Client object. 16 | 17 | ## [2.0.2] - 20230628 18 | Fix tests to consider member data attributes and other API changes 19 | Add missing class access modifiers. 20 | Update package lock to version 2. 21 | Bump dependencies based on security advisory. 22 | Implement ClientInterface and SocketFactoryInterface. 23 | Make ErrorCallback type more specific. 24 | 25 | ## [2.0.0] - 20221209 26 | Change onError callback signature (breaking). 27 | Change class access modifiers from private to protected. 28 | Add optional reconnect attempt in case of connection overflow. 29 | Add isServer argument to checkConnectionsOverflow. 30 | npm audit fix minimatch. 31 | 32 | ## [1.2.0] - 20221010 33 | Refactor throw "..." to throw new Error("...") 34 | Add SocketFactory + tests 35 | Add VirtualServer class to complement VirtualClient 36 | Implement getRemoteAddress, getRemotePort, getLocalPort, getLocalAddress for WSClient and VirtualClient 37 | Add optional closeClients parameter to Server.close() 38 | Improve self-signed cert generation script and add rejectUnauthorized tests 39 | Add TLS connection test suites 40 | Add TCP connection test suite covering IPv4 and IPv6 host validation 41 | Add TCP connection test suite covering IPv4 and IPv6 host validation 42 | Ensure WSClient IPv6 host gets surrounded with brackets during socketConnect 43 | Verify and set default error message when ws.ErrorEvent is undefined 44 | 45 | ## [1.1.1] - 20220516 46 | Audit npm packages version 47 | 48 | ## [1.1.0] - 20220516 49 | Fix timing bug about unreferenced variable. 50 | Add ByteSize class to await chunks of incoming data. 51 | Add Client.unRead function to be able to put read data back into buffer. 52 | 53 | ## [1.0.1] - 20210928 54 | Add configuration settings to allow direct import from node modules (npm) 55 | 56 | ## [1.0.0] - 20210928 57 | Test suite added. 58 | Wiki added. 59 | Examples added. 60 | README.md updated. 61 | AbstractClient class refactored to Client and declared as abstract class. 62 | AbstractServer class refactored to Server and declared as abstract class. 63 | 64 | ## [0.9.0] - 20210824 65 | First release. 66 | -------------------------------------------------------------------------------- /GENERATING_DOCS.md: -------------------------------------------------------------------------------- 1 | For each and every file under the _./src_ directory, run an isolated export command line as follows: 2 | ``` 3 | ./node_modules/.bin/typedoc --entryDocument Home.md --hideBreadcrumbs true --hideInPageTOC true --cleanOutputDir false ./src/$FILE_NAME_HERE.ts 4 | ``` 5 | 6 | Remove the modules index file, then copy the results over to the wiki repository: 7 | ``` 8 | rm ./docs/modules.md 9 | cp -r ./docs/* ../pocket-sockets.wiki/. 10 | ``` 11 | 12 | Updating the wiki repository: 13 | ``` 14 | cd ../pocket-sockets.wiki 15 | git add . 16 | git commit -S -m "Updating documentation to reflect latest code" 17 | git push origin master 18 | ``` 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2022 Thomas Backlund and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pocket-sockets 2 | 3 | A powerful and smooth client/server sockets library for browser and _Node.js_, written in TypeScript with very few dependencies. 4 | 5 | :heavy_check_mark: Written in TypeScript. 6 | 7 | :heavy_check_mark: Support for both _WebSockets_ and regular _TCP_ sockets with a unified interface. 8 | 9 | :heavy_check_mark: Works both in _NodeJS_ and browser. 10 | 11 | :heavy_check_mark: Supports SSL/TLS encryption with certificates. 12 | 13 | :heavy_check_mark: Test suite of 105 tests. 14 | 15 | ## WebSockets vs. regular TCP sockets 16 | _WebSockets_ are a must when using a browser, however plain _TCP_ sockets are faster and a good choice when no browser is involved. 17 | 18 | The overall interface for _pocket-sockets_ _WebSocket_ and _TCP_ sockets **are identical** so it is easy to switch between the underlying implementations. 19 | 20 | ## Example 21 | For a quick glimpse of what it looks like to set up a server that receives a string from clients, then replies back and closes the connection afterwards, follow the example below: 22 | ```typescript 23 | import {WSServer, WSClient, ClientInterface} from "pocket-sockets"; 24 | 25 | const server = new WSServer({ 26 | host: "localhost", 27 | port: 8181 28 | }); 29 | server.listen(); 30 | 31 | server.onConnection( (client: ClientInterface) => { 32 | client.onData( (data: Buffer | string) => { 33 | client.send("This is server: received!"); 34 | }); 35 | client.onClose( () => { 36 | server.close(); 37 | }); 38 | }); 39 | 40 | const client = new WSClient({ 41 | host: "localhost", 42 | port: 8181 43 | }); 44 | client.connect(); 45 | 46 | client.onConnect( () => { 47 | client.onData( (data: Buffer | string) => { 48 | client.close(); 49 | }); 50 | client.send("This is client: hello"); 51 | }); 52 | ``` 53 | 54 | For complete examples, please refer to the files under the [./example](https://github.com/bashlund/pocket-sockets/tree/main/example) directory. 55 | 56 | ## Run tests 57 | ```sh 58 | git clone https://github.com/bashlund/pocket-sockets.git 59 | cd pocket-sockets 60 | npm isntall 61 | cd ./test/cert/ && ./generate_self_signed_cert.sh && cd ../.. 62 | npm test 63 | ``` 64 | 65 | ## Run examples 66 | ```sh 67 | git clone https://github.com/bashlund/pocket-sockets.git 68 | cd pocket-sockets 69 | npm isntall 70 | npx ts-node ./example/example-ws.ts 71 | npx ts-node ./example/example-tcp.ts 72 | ``` 73 | 74 | ## Use in browser 75 | For browser examples, please refer to the files under the [./example](https://github.com/bashlund/pocket-sockets/tree/main/example) directory. 76 | 77 | ## NPM 78 | ```sh 79 | npm add --save pocket-sockets 80 | ``` 81 | 82 | ## Reference 83 | Code documentation and API references are available in the official [Wiki](https://github.com/bashlund/pocket-sockets/wiki): [https://github.com/bashlund/pocket-sockets/wiki](https://github.com/bashlund/pocket-sockets/wiki). 84 | 85 | ## Credits 86 | Lib written by @bashlund, tests and wiki nicely crafted by @filippsen. 87 | 88 | ## License 89 | This project is released under the _MIT_ license. 90 | -------------------------------------------------------------------------------- /example/example-browser-client.ts: -------------------------------------------------------------------------------- 1 | // 2 | // example-browser-client.ts 3 | // 4 | // Last tested with Parcel version 1.12.3 and example-browser-server.ts. 5 | // 6 | // Setup and build client: 7 | // npm add parcel@1.12.3 --save-dev 8 | // npx parcel build --no-minify --no-source-maps --out-dir build --public-url . --target browser ./example/example-browser.html 9 | // 10 | // Run server: 11 | // npx ts-node ./example/example-browser-server.ts 12 | // 13 | // Browse to _./build/example-browser.html_ 14 | // 15 | // Expected output: 16 | // pocket-sockets: WS browser client example 17 | // Client: connecting... 18 | // GET ws://localhost:8181/ 19 | // Client: connected 20 | // Client: incoming server data 21 | // Uint8Array(25) [ 84, 104, 105, 115, 32, 105, 115, 32, 115, 101, … ] 22 | // Client: closed 23 | // 24 | 25 | import {WSClient} from "../src/WSClient"; 26 | 27 | console.log("pocket-sockets: WS browser client example"); 28 | 29 | const clientOptions = { 30 | host: "localhost", 31 | port: 8181 32 | }; 33 | const client = new WSClient(clientOptions); 34 | client.connect(); 35 | console.log("Client: connecting..."); 36 | 37 | client.onConnect( () => { 38 | console.log("Client: connected"); 39 | client.onData( (data: Buffer) => { 40 | console.log("Client: incoming server data", data); 41 | client.close(); 42 | }); 43 | client.onClose( () => { 44 | console.log("Client: closed"); 45 | }); 46 | client.sendString("This is client: hello"); 47 | }); 48 | -------------------------------------------------------------------------------- /example/example-browser-server.ts: -------------------------------------------------------------------------------- 1 | // 2 | // example-browser-server.ts 3 | // 4 | // Run: npx ts-node ./example/example-browser-server.ts 5 | // 6 | // Expected output: 7 | // pocket-sockets: WS server example 8 | // Server: listening... 9 | // Server: socket accepted 10 | // Server: incoming client data 11 | // Server: client connection closed 12 | // 13 | 14 | import {WSServer} from "../src/WSServer"; 15 | import {Client} from "../src/Client"; 16 | 17 | console.log("pocket-sockets: WS server example"); 18 | const serverOptions = { 19 | host: "0.0.0.0", 20 | port: 8181 21 | }; 22 | const server = new WSServer(serverOptions); 23 | server.listen(); 24 | console.log("Server: listening..."); 25 | 26 | server.onConnection( (client: Client) => { 27 | console.log("Server: socket accepted"); 28 | client.onData( (data: Buffer) => { 29 | console.log("Server: incoming client data", data); 30 | client.sendString("This is server: received!"); 31 | }); 32 | client.onClose( () => { 33 | console.log("Server: client connection closed"); 34 | server.close(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /example/example-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | pocket-sockets (Example) 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/example-readme.ts: -------------------------------------------------------------------------------- 1 | // example-readme.ts 2 | // Run: npx ts-node ./example/example-readme.ts 3 | 4 | import {WSServer, WSClient, Client} from "../index"; 5 | 6 | const server = new WSServer({ 7 | host: "localhost", 8 | port: 8181 9 | }); 10 | server.listen(); 11 | 12 | server.onConnection( (client: Client) => { 13 | client.onData( (data: Buffer) => { 14 | client.sendString("This is server: received!"); 15 | }); 16 | client.onClose( () => { 17 | server.close(); 18 | }); 19 | }); 20 | 21 | const client = new WSClient({ 22 | host: "localhost", 23 | port: 8181 24 | }); 25 | client.connect(); 26 | 27 | client.onConnect( () => { 28 | client.onData( (data: Buffer) => { 29 | client.close(); 30 | }); 31 | client.sendString("This is client: hello"); 32 | }); 33 | 34 | console.log("pocket-sockets OK"); 35 | -------------------------------------------------------------------------------- /example/example-tcp.ts: -------------------------------------------------------------------------------- 1 | // 2 | // example-tcp.ts 3 | // 4 | // Run: npx ts-node ./example/example-tcp.ts 5 | // 6 | // Expected output: 7 | // pocket-sockets: TCP example 8 | // Server: listening... 9 | // Client: connecting... 10 | // Server: socket accepted 11 | // Client: connected 12 | // Server: incoming client data 13 | // Client: incoming server data 14 | // Server: client connection closed 15 | // Client: closed 16 | // 17 | 18 | import {TCPServer, TCPClient, ClientInterface} from "../index"; 19 | 20 | console.log("pocket-sockets: TCP example"); 21 | const serverOptions = { 22 | host: "localhost", 23 | port: 8181, 24 | // If wanting to send/recieve in text-mode set textMode to true. 25 | //textMode: true, 26 | }; 27 | const server = new TCPServer(serverOptions); 28 | server.listen(); 29 | console.log("Server: listening..."); 30 | 31 | server.onConnection( (client: ClientInterface) => { 32 | console.log("Server: socket accepted"); 33 | client.onData( (data: Buffer | string) => { 34 | console.log("Server: incoming client data", data); 35 | client.send("This is server: received!"); 36 | }); 37 | client.onClose( () => { 38 | console.log("Server: client connection closed"); 39 | server.close(); 40 | }); 41 | }); 42 | 43 | const clientOptions = { 44 | host: "localhost", 45 | port: 8181, 46 | // If wanting to send/recieve in text-mode set textMode to true. 47 | //textMode: true, 48 | }; 49 | const client = new TCPClient(clientOptions); 50 | client.connect(); 51 | console.log("Client: connecting..."); 52 | 53 | client.onConnect( () => { 54 | console.log("Client: connected"); 55 | client.onData( (data: Buffer | string) => { 56 | console.log("Client: incoming server data", data); 57 | client.close(); 58 | }); 59 | client.onClose( () => { 60 | console.log("Client: closed"); 61 | }); 62 | client.send("This is client: hello"); 63 | }); 64 | -------------------------------------------------------------------------------- /example/example-ws.ts: -------------------------------------------------------------------------------- 1 | // 2 | // example-ws.ts 3 | // 4 | // Run: npx ts-node ./example/example-ws.ts 5 | // 6 | // Expected output: 7 | // pocket-sockets: WS example 8 | // Server: listening... 9 | // Client: connecting... 10 | // Server: socket accepted 11 | // Client: connected 12 | // Server: incoming client data 13 | // Client: incoming server data 14 | // Client: closed 15 | // Server: client connection closed 16 | // 17 | 18 | import {WSServer, WSClient, ClientInterface} from "../index"; 19 | 20 | console.log("pocket-sockets: WS example"); 21 | const serverOptions = { 22 | host: "localhost", 23 | port: 8181, 24 | // If wanting to send/recieve in text-mode set textMode to true. 25 | //textMode: true, 26 | }; 27 | const server = new WSServer(serverOptions); 28 | server.listen(); 29 | console.log("Server: listening..."); 30 | 31 | server.onConnection( (client: ClientInterface) => { 32 | console.log("Server: socket accepted"); 33 | client.onData( (data: Buffer | string) => { 34 | console.log("Server: incoming client data", data); 35 | client.send("This is server: received!"); 36 | }); 37 | client.onClose( () => { 38 | console.log("Server: client connection closed"); 39 | server.close(); 40 | }); 41 | }); 42 | 43 | const clientOptions = { 44 | host: "localhost", 45 | port: 8181, 46 | // If wanting to send/recieve in text-mode set textMode to true. 47 | //textMode: true, 48 | }; 49 | const client = new WSClient(clientOptions); 50 | client.connect(); 51 | console.log("Client: connecting..."); 52 | 53 | client.onConnect( () => { 54 | console.log("Client: connected"); 55 | client.onData( (data: Buffer | string) => { 56 | console.log("Client: incoming server data", data); 57 | client.close(); 58 | }); 59 | client.onClose( () => { 60 | console.log("Client: closed"); 61 | }); 62 | client.send("This is client: hello"); 63 | }); 64 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src"; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pocket-sockets", 3 | "version": "4.0.0", 4 | "description": "A powerful and smooth client/server sockets library for browser and NodeJS. Supports both WebSockets and regular TCP sockets, TLS encrypted or not.", 5 | "keywords": [ 6 | "sockets", 7 | "library", 8 | "browser", 9 | "nodejs", 10 | "websockets", 11 | "raw", 12 | "tcp", 13 | "encrypted", 14 | "encryption", 15 | "networking", 16 | "communication", 17 | "layer" 18 | ], 19 | "homepage": "", 20 | "author": "Thomas Backlund", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/bashlund/pocket-sockets.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/bashlund/pocket-sockets/issues" 27 | }, 28 | "private": false, 29 | "license": "MIT", 30 | "engines": { 31 | "node": ">=12" 32 | }, 33 | "main": "build/index.js", 34 | "types": "build/index.d.ts", 35 | "scripts": { 36 | "test": "./node_modules/.bin/testyts", 37 | "docs": "./node_modules/.bin/typedoc --entryDocument Home.md --hideBreadcrumbs true --hideInPageTOC true --cleanOutputDir false ./src/*.ts", 38 | "build": "./node_modules/.bin/tsc", 39 | "tsc": "./node_modules/.bin/tsc", 40 | "lint": "npx eslint ./src", 41 | "prepublishOnly": "tsc" 42 | }, 43 | "dependencies": { 44 | "ws": "^8.16.0" 45 | }, 46 | "devDependencies": { 47 | "@tsconfig/node12": "^1.0.7", 48 | "@types/node": "^14.17.11", 49 | "@types/ws": "^8.5.10", 50 | "@typescript-eslint/eslint-plugin": "^6.19.1", 51 | "@typescript-eslint/parser": "^6.19.1", 52 | "eslint": "^8.56.0", 53 | "testyts": "^1.3.0", 54 | "ts-node": "^9.1.1", 55 | "typedoc": "^0.22.15", 56 | "typedoc-plugin-markdown": "^3.11.0", 57 | "typescript": "^4.3.5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/ByteSize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This can be used to await X nr of bytes on the client socket. 3 | * It is allowed to use text mode sockets, but the data resolve will always be as Buffer. 4 | * 5 | */ 6 | import {ClientInterface} from "./types"; 7 | 8 | export class ByteSize 9 | { 10 | protected client: ClientInterface; 11 | protected data: Buffer; 12 | protected resolve?: (data: Buffer) => void; 13 | protected reject?: (error: Error) => void; 14 | protected ended: boolean; 15 | protected nrBytes: number = 0; 16 | protected timeoutId?: ReturnType; 17 | 18 | constructor(client: ClientInterface) { 19 | this.client = client; 20 | this.data = Buffer.alloc(0); 21 | this.client.onData(this.onData); 22 | this.client.onClose(this.onClose); 23 | this.ended = false; 24 | } 25 | 26 | /** 27 | * @param nrBytes nr bytes to wait for and resolve. The reminder is "unread" back to socket buffer. 28 | * If set to -1 then wait for any data and resolve all data available at that point. 29 | */ 30 | public async read(nrBytes: number, timeout: number = 3000): Promise { 31 | if (this.ended || this.timeoutId) { 32 | throw new Error("Cannot reuse a ByteSize"); 33 | } 34 | this.nrBytes = nrBytes; 35 | if (timeout) { 36 | this.timeoutId = setTimeout( () => { 37 | delete this.timeoutId; 38 | if (this.reject) { 39 | const reject = this.reject; 40 | this.end(); 41 | reject(new Error("Timeout")); 42 | } 43 | }, timeout); 44 | } 45 | return new Promise( (resolve, reject) => { 46 | this.resolve = resolve; 47 | this.reject = reject; 48 | this.onData(Buffer.alloc(0)); 49 | }); 50 | } 51 | 52 | protected onClose = () => { 53 | if (this.reject) { 54 | const reject = this.reject; 55 | this.end(); 56 | reject(new Error("Socket closed")); 57 | } 58 | } 59 | 60 | protected onData = (buf: Buffer | string) => { 61 | if (this.ended) { 62 | return; 63 | } 64 | 65 | if (!Buffer.isBuffer(buf)) { 66 | buf = Buffer.from(buf); 67 | } 68 | 69 | this.data = Buffer.concat([this.data, buf]); 70 | if (!this.resolve) { 71 | return; 72 | } 73 | 74 | if (this.data.length >= this.nrBytes) { 75 | if (this.nrBytes < 0 && this.data.length === 0) { 76 | return; 77 | } 78 | 79 | const nrBytes = this.nrBytes < 0 ? this.data.length : this.nrBytes; 80 | 81 | const bite = this.data.slice(0, nrBytes); 82 | this.data = this.data.slice(nrBytes); 83 | const resolve = this.resolve; 84 | this.end(); 85 | resolve(bite); 86 | } 87 | } 88 | 89 | protected end() { 90 | if (this.ended) { 91 | return; 92 | } 93 | this.ended = true; 94 | if (this.timeoutId) { 95 | clearTimeout(this.timeoutId); 96 | } 97 | this.client.offData(this.onData); 98 | this.client.offClose(this.onClose); 99 | delete this.reject; 100 | delete this.resolve; 101 | if (this.client.isTextMode()) { 102 | this.client.unRead(this.data.toString()); 103 | } 104 | else { 105 | this.client.unRead(this.data); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClientOptions, 3 | SocketErrorCallback, 4 | SocketDataCallback, 5 | SocketConnectCallback, 6 | SocketCloseCallback, 7 | ClientInterface, 8 | } from "./types"; 9 | 10 | /** 11 | * Class for wrapping a socket (TCP, Websocket or Virtual) under a common interface. 12 | * 13 | * Socket specific functions need to be overridden/implemented in dervived classes. 14 | */ 15 | export abstract class Client implements ClientInterface 16 | { 17 | protected clientOptions?: ClientOptions; 18 | protected eventHandlers: {[key: string]: [((data: any) => void)[], (Buffer | undefined)[]]}; 19 | protected _isClosed: boolean = false; 20 | 21 | constructor(clientOptions?: ClientOptions) { 22 | this.clientOptions = clientOptions; 23 | this.eventHandlers = {}; 24 | } 25 | 26 | /** 27 | * This should be called on (wrapped) clients before using. 28 | * For regular TCP and WebSocket clients this function does nothing. 29 | */ 30 | public async init() { 31 | // Do nothing, but allows wrapping sockets to do something. 32 | } 33 | 34 | public getSocket(): any { 35 | throw new Error("Function not implemented."); 36 | } 37 | 38 | public isWebSocket(): boolean { 39 | return false; 40 | } 41 | 42 | /** 43 | * @returns true if set to text mode, false if binary mode (default). 44 | */ 45 | public isTextMode(): boolean { 46 | return this.clientOptions?.textMode ?? false; 47 | } 48 | 49 | /** 50 | * Connect to server. 51 | * 52 | */ 53 | public connect() { 54 | this.socketConnect(); 55 | this.socketHook(); 56 | } 57 | 58 | /** 59 | * Send buffer on socket. 60 | * 61 | * @param {data} data to be sent 62 | * For TCP sockets strings are always converted to Buffers before sending. 63 | * 64 | * For WebSockets in binary mode strings are converted to Buffers before sending. 65 | * 66 | * For WebSockets in binary mode Buffers are sent as they are in binary mode on the WebSocket. 67 | * 68 | * For WebSockets in text mode strings are sent as they are in text mode on the WebSocket. 69 | * 70 | * For WebSockets in text mode Buffers are converted into strings and sent in text mode 71 | * on the WebSocket. 72 | * 73 | * @throws An error will be thrown when buffer data type is incompatible. 74 | */ 75 | public send(data: Buffer | string) { 76 | if (this._isClosed) { 77 | return; 78 | } 79 | 80 | this.socketSend(data); 81 | } 82 | 83 | /** 84 | * Close socket. 85 | */ 86 | public close() { 87 | if (this._isClosed) { 88 | return; 89 | } 90 | 91 | this.socketClose(); 92 | } 93 | 94 | public isClosed(): boolean { 95 | return this._isClosed; 96 | } 97 | 98 | /** 99 | * User hook for socket connection error. 100 | * 101 | * @param {Function} fn - on error callback. Function is passed a string with the error message. 102 | * 103 | */ 104 | public onError(fn: SocketErrorCallback) { 105 | this.on("error", fn); 106 | } 107 | 108 | /** 109 | * Unhook handler for socket errors. 110 | * 111 | * @param {Function} fn - remove existing error callback 112 | * 113 | */ 114 | public offError(fn: SocketErrorCallback) { 115 | this.off("error", fn); 116 | } 117 | 118 | /** 119 | * User hook for incoming data. 120 | * 121 | * For sockets in binary mode data in the callback is always Buffer. 122 | * For sockets in text mode data in the callback is always string. 123 | * 124 | * @param {Function} fn - on data callback. Function is passed a Buffer object. 125 | */ 126 | public onData(fn: SocketDataCallback) { 127 | this.on("data", fn); 128 | } 129 | 130 | /** 131 | * Unhook handler for incoming data. 132 | * 133 | * @param {Function} fn - remove data callback. 134 | * 135 | */ 136 | public offData(fn: SocketDataCallback) { 137 | this.off("data", fn); 138 | } 139 | 140 | /** 141 | * User hook for connection event. 142 | * 143 | * @param {Function} fn - on connect callback. 144 | * 145 | */ 146 | public onConnect(fn: SocketConnectCallback) { 147 | this.on("connect", fn); 148 | } 149 | 150 | /** 151 | * Unhook handler for connection event. 152 | * 153 | * @param {Function} fn - remove connect callback. 154 | * 155 | */ 156 | public offConnect(fn: SocketConnectCallback) { 157 | this.off("connect", fn); 158 | } 159 | 160 | /** 161 | * User hook for close event. 162 | * 163 | * @param {Function} fn - on close callback. 164 | * 165 | */ 166 | public onClose(fn: SocketCloseCallback) { 167 | this.on("close", fn); 168 | } 169 | 170 | /** 171 | * Unhook handler for close event. 172 | * 173 | * @param {Function} fn - remove close callback. 174 | * 175 | */ 176 | public offClose(fn: SocketCloseCallback) { 177 | this.off("close", fn); 178 | } 179 | 180 | public getLocalAddress(): string | undefined { 181 | // Override in implementation if applicable 182 | return undefined; 183 | } 184 | 185 | public getRemoteAddress(): string | undefined { 186 | // Override in implementation if applicable 187 | return undefined; 188 | } 189 | 190 | public getRemotePort(): number | undefined { 191 | // Override in implementation if applicable 192 | return undefined; 193 | } 194 | 195 | public getLocalPort(): number | undefined { 196 | // Override in implementation if applicable 197 | return undefined; 198 | } 199 | 200 | /** 201 | * Unread data by putting it back into the event queue. 202 | * @param {Buffer} data 203 | */ 204 | public unRead(data: Buffer | string) { 205 | if (this.isTextMode()) { 206 | if (typeof(data) !== "string") { 207 | throw new Error("unRead expecting string in text mode"); 208 | } 209 | } 210 | else { 211 | if (!Buffer.isBuffer(data)) { 212 | throw new Error("unRead expecting Buffer in binary mode"); 213 | } 214 | } 215 | 216 | if (data.length === 0) { 217 | return; 218 | } 219 | 220 | const bufferIncomingData = this.clientOptions?.bufferData === undefined ? true : 221 | this.clientOptions.bufferData; 222 | 223 | const dataEvent: Parameters = [data]; 224 | 225 | this.triggerEvent("data", ...dataEvent, bufferIncomingData, true); 226 | } 227 | 228 | /** 229 | * Create the socket object and initiate a connection. 230 | * This only done for initiating client sockets. 231 | * A server listener socket client is already connected and must be passed in the constructor. 232 | */ 233 | protected socketConnect() { 234 | throw new Error("Function not implemented."); 235 | } 236 | 237 | /** 238 | * Hook events on the socket. 239 | */ 240 | protected socketHook() { 241 | throw new Error("Function not implemented."); 242 | } 243 | 244 | /** 245 | * Send the given buffer on socket. 246 | * Socket specific implementation. 247 | */ 248 | protected socketSend(data: Buffer | string) { // eslint-disable-line @typescript-eslint/no-unused-vars 249 | throw new Error("Function not implemented."); 250 | } 251 | 252 | /** 253 | * Socket-specific close procedure. 254 | */ 255 | protected socketClose() { 256 | throw new Error("Function not implemented."); 257 | } 258 | 259 | /** 260 | * Base close event procedure responsible for triggering the close event. 261 | */ 262 | protected socketClosed = (hadError: boolean) => { 263 | this._isClosed = true; 264 | 265 | const closeEvent: Parameters = [hadError]; 266 | 267 | this.triggerEvent("close", ...closeEvent); 268 | } 269 | 270 | /** 271 | * Base data event procedure responsible for triggering the data event. 272 | * 273 | * @param {Buffer} data - data buffer. 274 | * 275 | */ 276 | protected socketData = (data: Buffer | string) => { 277 | if (this.isTextMode()) { 278 | if (Buffer.isBuffer(data)) { 279 | data = data.toString(); 280 | } 281 | } 282 | else { 283 | if (typeof(data) === "string") { 284 | data = Buffer.from(data); 285 | } 286 | } 287 | 288 | const bufferIncomingData = this.clientOptions?.bufferData === undefined ? true : 289 | this.clientOptions.bufferData; 290 | 291 | const dataEvent: Parameters = [data]; 292 | 293 | this.triggerEvent("data", ...dataEvent, bufferIncomingData); 294 | } 295 | 296 | /** 297 | * Base connect event procedure responsible for triggering the connect event. 298 | */ 299 | protected socketConnected = () => { 300 | this.unhookError(); 301 | 302 | this.triggerEvent("connect"); 303 | } 304 | 305 | /** 306 | * The error handler should be unhooked after connection to not confuse 307 | * abrupt close with connect error. 308 | */ 309 | protected unhookError() { 310 | throw new Error("Function not implemented"); 311 | } 312 | 313 | /** 314 | * Base error event procedure responsible for triggering the error event. 315 | * 316 | * @param {Buffer} data - error message. 317 | * 318 | */ 319 | protected socketError = (message: string) => { 320 | const errorEvent: Parameters = [message]; 321 | 322 | this.triggerEvent("error", ...errorEvent); 323 | } 324 | 325 | /** 326 | * Base "off" event procedure responsible for removing a callback from the list of event handlers. 327 | * 328 | * @param {string} event - event name. 329 | * @param {Function} fn - callback. 330 | * 331 | */ 332 | protected off(event: string, fn: (data: any) => void) { 333 | const [fns] = (this.eventHandlers[event] || [[], []]); 334 | const index = fns.indexOf(fn); 335 | if (index > -1) { 336 | fns.splice(index, 1); 337 | } 338 | } 339 | 340 | /** 341 | * Base "on" event procedure responsible for adding a callback to the list of event handlers. 342 | * 343 | * @param {string} event - event name. 344 | * @param {Function} fn - callback. 345 | * 346 | */ 347 | protected on(event: string, fn: (data: any) => void) { 348 | const tuple = (this.eventHandlers[event] || [[], []]); 349 | this.eventHandlers[event] = tuple; 350 | const [fns, queue] = tuple; 351 | if (fns.length === 0) { 352 | // Send buffered up events. 353 | queue.forEach( (event: any) => { 354 | fn(event); 355 | }); 356 | queue.length = 0; 357 | } 358 | fns.push(fn); 359 | } 360 | 361 | /** 362 | * Trigger event calls the appropriate handler based on the event name. 363 | * 364 | * @param {string} event - event name. 365 | * @param {Buffer} [data] - event data. 366 | * @param {boolean} [doBuffer] - buffers up event data. 367 | * @param {boolean} [invertOrder] - used for "unreading" an event and puts it first in the 368 | * queue (if doBuffer is true) 369 | * 370 | */ 371 | protected triggerEvent(event: string, data?: any, doBuffer = false, invertOrder = false) { 372 | const tuple = (this.eventHandlers[event] || [[], []]); 373 | this.eventHandlers[event] = tuple; 374 | const [fns, queue] = tuple; 375 | if (fns.length === 0) { 376 | if (doBuffer) { 377 | // Buffer up the event 378 | if (invertOrder) { 379 | queue.unshift(data); 380 | } 381 | else { 382 | queue.push(data); 383 | } 384 | } 385 | } 386 | else { 387 | fns.forEach( (fn) => { 388 | fn(data); 389 | }); 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/Server.ts: -------------------------------------------------------------------------------- 1 | import {ClientInterface} from "./types"; 2 | import { 3 | ServerOptions, 4 | SocketErrorCallback, 5 | SocketCloseCallback, 6 | SocketAcceptCallback, 7 | } from "./types"; 8 | 9 | /** 10 | * Boilerplate for creating and wrapping a server socket listener (TCP or Websocket) under a common interface. 11 | * 12 | * Socket specific functions need to be overridden/implemented. 13 | * 14 | */ 15 | export abstract class Server 16 | { 17 | protected serverOptions: ServerOptions; 18 | protected eventHandlers: {[key: string]: ((data: any) => void)[]}; 19 | protected _isClosed: boolean; 20 | protected clients: ClientInterface[]; 21 | 22 | constructor(serverOptions: ServerOptions) { 23 | this.serverOptions = serverOptions; 24 | this.eventHandlers = {}; 25 | this.clients = []; 26 | this._isClosed = false; 27 | } 28 | 29 | /** 30 | * Listens for connections and yields connected client sockets. 31 | * 32 | */ 33 | public listen() { 34 | if (this.serverOptions.port === 0) { 35 | throw new Error("Server socket not allowed to listen to port 0."); 36 | } 37 | 38 | this.serverListen(); 39 | } 40 | 41 | /** 42 | * Close listener and optionally (default) also all accepted socket clients. 43 | * @param closeClients if set to true (default) then also close all accepted sockets, 44 | * if set to false then leave accepted client sockets open. 45 | */ 46 | public close(closeClients: boolean = true) { 47 | if (this._isClosed) { 48 | return; 49 | } 50 | this.serverClose(); 51 | if (closeClients) { 52 | this.clients.forEach( client => client.close() ); 53 | this.clients = []; 54 | } 55 | } 56 | 57 | /** 58 | * Event handler triggered when client has connected. 59 | * 60 | * A Client object is passed as argument to fn() of the instance type this.SocketClass. 61 | * 62 | * @param {Function} fn callback 63 | */ 64 | public onConnection(fn: SocketAcceptCallback) { 65 | this.on("connection", fn); 66 | } 67 | 68 | /** 69 | * Event handler triggered when a server error occurs. 70 | * 71 | * An error string is passed as argument to fn(). 72 | * 73 | * @param {Function} fn callback 74 | */ 75 | public onError(fn: SocketErrorCallback) { 76 | this.on("error", fn); 77 | } 78 | 79 | /** 80 | * Event handler triggered when server has closed together with all its client sockets. 81 | * 82 | * @param {Function} fn callback 83 | */ 84 | public onClose(fn: SocketCloseCallback) { 85 | this.on("close", fn); 86 | } 87 | 88 | public isClosed(): boolean { 89 | return this._isClosed; 90 | } 91 | 92 | /** 93 | * Create the server socket. 94 | */ 95 | protected serverCreate() { 96 | throw new Error("Not implemented."); 97 | } 98 | 99 | /** 100 | * Initiate the server listener. 101 | */ 102 | protected serverListen() { 103 | throw new Error("Not implemented."); 104 | } 105 | 106 | /** 107 | * Close the server. 108 | * Override as necessary. 109 | */ 110 | protected serverClose() { 111 | throw new Error("Not implemented."); 112 | } 113 | 114 | /** 115 | * Internal error event implementation. 116 | * 117 | * @param {Error} err 118 | */ 119 | protected serverError = (message: string) => { 120 | const errorEvent: Parameters = [message]; 121 | 122 | this.triggerEvent("error", ...errorEvent); 123 | } 124 | 125 | /** 126 | * Internal close event implementation. 127 | */ 128 | protected serverClosed = () => { 129 | this._isClosed = true; 130 | this.triggerEvent("close"); 131 | } 132 | 133 | /** 134 | * Performs all operations involved in registering a new client connection. 135 | * 136 | * @param {ClientInterface} client 137 | */ 138 | protected addClient(client: ClientInterface) { 139 | this.clients.push(client); 140 | client.onClose( () => { this.removeClient(client) } ); 141 | 142 | const socketAcceptedEvent: Parameters = [client]; 143 | 144 | this.triggerEvent("connection", ...socketAcceptedEvent); 145 | } 146 | 147 | /** 148 | * Performs all operations involved in removing an existing client registration. 149 | * 150 | * @param {ClientInterface} client 151 | */ 152 | protected removeClient(client: ClientInterface) { 153 | const index = this.clients.indexOf(client); 154 | if (index > -1) { 155 | this.clients.splice(index, 1) 156 | } 157 | } 158 | 159 | /** 160 | * Internal event implementation. 161 | * 162 | * @param {string} event 163 | * @param {Function} fn 164 | */ 165 | public on(event: string, fn: (data: any) => void) { 166 | const fns = this.eventHandlers[event] || []; 167 | this.eventHandlers[event] = fns; 168 | fns.push(fn); 169 | } 170 | 171 | /** 172 | * Internal event trigger implementation. 173 | * 174 | * @param {string} event 175 | * @param {any} data 176 | */ 177 | protected triggerEvent(event: string, data?: any) { 178 | const fns = this.eventHandlers[event] || []; 179 | fns.forEach( fn => { 180 | fn(data); 181 | }); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/SocketFactory.ts: -------------------------------------------------------------------------------- 1 | let TCPClient: any; 2 | let TCPServer: any; 3 | let WSServer: any; 4 | 5 | // Add browser/browserify/parcel dependency 6 | // @ts-ignore 7 | if(typeof "process" === "undefined" && !process && !process.versions && !process.versions.node) { 8 | require("regenerator-runtime/runtime"); 9 | } 10 | else { 11 | // Import nodejs modules. 12 | TCPClient = require("./TCPClient").TCPClient; //eslint-disable-line @typescript-eslint/no-var-requires 13 | WSServer = require("./WSServer").WSServer; //eslint-disable-line @typescript-eslint/no-var-requires 14 | TCPServer = require("./TCPServer").TCPServer; //eslint-disable-line @typescript-eslint/no-var-requires 15 | } 16 | 17 | import { 18 | Server, 19 | } from "./Server"; 20 | 21 | import { 22 | WSClient, 23 | } from "./WSClient"; 24 | 25 | import { 26 | SocketFactoryConfig, 27 | SocketFactoryStats, 28 | ClientInterface, 29 | SocketFactoryInterface, 30 | EVENT_SOCKETFACTORY_ERROR, 31 | EVENT_SOCKETFACTORY_CLOSE, 32 | EVENT_SOCKETFACTORY_CONNECT, 33 | EVENT_SOCKETFACTORY_CLIENT_INIT_ERROR, 34 | EVENT_SOCKETFACTORY_CLIENT_CONNECT_ERROR, 35 | EVENT_SOCKETFACTORY_CLIENT_IP_REFUSE, 36 | EVENT_SOCKETFACTORY_SERVER_INIT_ERROR, 37 | EVENT_SOCKETFACTORY_SERVER_LISTEN_ERROR, 38 | DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_DENIED, 39 | DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_NOT_ALLOWED, 40 | DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_OVERFLOW, 41 | SocketFactoryErrorCallback, 42 | SocketFactoryClientInitErrorCallback, 43 | SocketFactoryClientConnectErrorCallback, 44 | SocketFactoryConnectCallback, 45 | SocketFactoryCloseCallback, 46 | SocketFactoryServerInitErrorCallback, 47 | SocketFactoryServerListenErrorCallback, 48 | SocketFactoryClientIPRefuseCallback, 49 | SOCKET_WEBSOCKET, 50 | SOCKET_TCP, 51 | } from "./types"; 52 | 53 | /** 54 | * A SocketFactory emits connected sockets. 55 | * 56 | * An use case for SocketFactory is for peer-to-peer systems where one wants to abstract the difference 57 | * between client and server peers and instead of working with specific client or server sockets one 58 | * could offload that to the SocketFactory to be able to have a unified interface towards sockets, 59 | * because both parties can be client and server simultaneously. 60 | * 61 | * A SocketFactory is created for client sockets and/or server listener sockets. 62 | * The factory has the same interface for both client and server socket and they share any limits. 63 | * 64 | * A SocketFactory keep statistics on connections to not exceed the imposed limits on the factory. 65 | * The stats object can be shared between multiple SocketFactory instances so that they share the same rations. 66 | * 67 | * Although a client SocketFactory will at most initiate a single client socket (making the difference to directly 68 | * using a client socket small) the relevant difference is a unified interface for client and server and also 69 | * importantly that a client SocketFactory can share stats within and with other factories to avoid redundant connections. 70 | */ 71 | export class SocketFactory implements SocketFactoryInterface { 72 | protected config: SocketFactoryConfig; 73 | protected stats: SocketFactoryStats; 74 | protected handlers: {[type: string]: ((data?: any) => void)[]}; 75 | protected serverClientSockets: ClientInterface[]; 76 | protected serverSocket?: Server; 77 | protected clientSocket?: ClientInterface; 78 | protected _isClosed: boolean; 79 | protected _isShutdown: boolean; 80 | 81 | /** 82 | * @param config provided client or server config. 83 | * The config object can afterwards be directly manipulated on to 84 | * change behaviors.such as maxConnections and denylist. 85 | * @param stats provide this to share stats with other factories. 86 | */ 87 | constructor(config: SocketFactoryConfig, stats?: SocketFactoryStats) { 88 | this.config = config; 89 | this.stats = stats ?? {counters: {}}; 90 | this.handlers = {}; 91 | this.serverClientSockets = []; 92 | this._isClosed = false; 93 | this._isShutdown = false; 94 | } 95 | 96 | /** 97 | * For client factories initiate client connection. 98 | * For server factories setup server socket. 99 | * A factory can be both client and server. 100 | * Idempotent function, can be called again if client or server has been added, 101 | * or to manually reconnect a client if reconnectDelay was not set. 102 | */ 103 | public init() { 104 | if (this.config.server) { 105 | this.openServer(); 106 | } 107 | 108 | if (this.config.client) { 109 | this.connectClient(); 110 | } 111 | } 112 | 113 | public getSocketFactoryConfig(): SocketFactoryConfig { 114 | return this.config; 115 | } 116 | 117 | /** 118 | * Initiate the client socket. 119 | */ 120 | protected connectClient() { 121 | if (this.clientSocket || this._isClosed || this._isShutdown || !this.config.client) { 122 | return; 123 | } 124 | 125 | if (this.checkConnectionsOverflow(this.config.client.clientOptions.host ?? "localhost")) { 126 | if (this.config.client?.reconnectDelay ?? 0 > 0) { 127 | const delay = this.config.client!.reconnectDelay! * 1000; 128 | setTimeout( () => this.connectClient(), delay); 129 | } 130 | return; 131 | } 132 | 133 | try { 134 | this.clientSocket = this.createClientSocket(); 135 | } 136 | catch(error) { 137 | const clientInitErrorEvent: Parameters = [error as Error]; 138 | 139 | this.triggerEvent(EVENT_SOCKETFACTORY_CLIENT_INIT_ERROR, ...clientInitErrorEvent); 140 | 141 | const errorEvent: Parameters = 142 | [EVENT_SOCKETFACTORY_CLIENT_INIT_ERROR, error as Error]; 143 | 144 | this.triggerEvent(EVENT_SOCKETFACTORY_ERROR, ...errorEvent); 145 | 146 | return; 147 | } 148 | 149 | this.initClientSocket(); 150 | } 151 | 152 | /** 153 | * @throws 154 | */ 155 | protected createClientSocket(): ClientInterface { 156 | if (this.config.client?.socketType === SOCKET_WEBSOCKET) { 157 | return new WSClient(this.config.client.clientOptions); 158 | } 159 | else if (this.config.client?.socketType === SOCKET_TCP) { 160 | if (!TCPClient) { 161 | throw new Error("TCPClient class not available."); 162 | } 163 | return new TCPClient(this.config.client.clientOptions); 164 | } 165 | throw new Error("Misconfiguration"); 166 | } 167 | 168 | protected initClientSocket() { 169 | const socket = this.clientSocket; 170 | if (!socket) { 171 | return; 172 | } 173 | 174 | socket.onError( (errorMessage: string) => { 175 | if (socket === this.clientSocket) { 176 | delete this.clientSocket; 177 | } 178 | 179 | const error = new Error(errorMessage); 180 | 181 | const clientConnectErrorEvent: Parameters = [error]; 182 | 183 | this.triggerEvent(EVENT_SOCKETFACTORY_CLIENT_CONNECT_ERROR, 184 | ...clientConnectErrorEvent); 185 | 186 | const errorEvent: Parameters = 187 | [EVENT_SOCKETFACTORY_CLIENT_CONNECT_ERROR, error as Error]; 188 | 189 | this.triggerEvent(EVENT_SOCKETFACTORY_ERROR, ...errorEvent); 190 | 191 | if (this.config.client?.reconnectDelay ?? 0 > 0) { 192 | const delay = this.config.client!.reconnectDelay! * 1000; 193 | setTimeout( () => this.connectClient(), delay); 194 | } 195 | }); 196 | socket.onConnect( () => { 197 | if (!socket || !this.config.client) { 198 | return; 199 | } 200 | 201 | // Check the limits once more when connected since there 202 | // might have been accepted server connections happening. 203 | if (this.checkConnectionsOverflow(this.config.client.clientOptions.host ?? "localhost")) { 204 | delete this.clientSocket; 205 | socket.close(); 206 | if (this.config.client?.reconnectDelay ?? 0 > 0) { 207 | const delay = this.config.client!.reconnectDelay! * 1000; 208 | setTimeout( () => this.connectClient(), delay); 209 | } 210 | 211 | return; 212 | } 213 | socket.onClose( (hadError: boolean) => { 214 | if (!this.config.client) { 215 | return; 216 | } 217 | 218 | if (socket === this.clientSocket) { 219 | delete this.clientSocket; 220 | } 221 | 222 | this.decreaseConnectionsCounter(this.config.client.clientOptions.host ?? "localhost"); 223 | 224 | const closeEvent: Parameters = [socket, false, hadError]; 225 | 226 | this.triggerEvent(EVENT_SOCKETFACTORY_CLOSE, ...closeEvent); 227 | 228 | if (this.config.client?.reconnectDelay ?? 0 > 0) { 229 | const delay = this.config.client!.reconnectDelay! * 1000; 230 | setTimeout( () => this.connectClient(), delay); 231 | } 232 | }); 233 | 234 | this.increaseConnectionsCounter(this.config.client.clientOptions.host ?? "localhost"); 235 | 236 | const connectEvent: Parameters = [socket, false]; 237 | 238 | this.triggerEvent(EVENT_SOCKETFACTORY_CONNECT, ...connectEvent); 239 | }); 240 | socket.connect(); 241 | } 242 | 243 | protected openServer() { 244 | if (this._isClosed || this._isShutdown || !this.config.server || this.serverSocket) { 245 | return; 246 | } 247 | 248 | try { 249 | this.serverSocket = this.createServerSocket(); 250 | } 251 | catch(error) { 252 | const serverInitErrorEvent: Parameters = [error as Error]; 253 | 254 | this.triggerEvent(EVENT_SOCKETFACTORY_SERVER_INIT_ERROR, ...serverInitErrorEvent); 255 | 256 | const errorEvent: Parameters = 257 | [EVENT_SOCKETFACTORY_SERVER_INIT_ERROR, error as Error]; 258 | 259 | this.triggerEvent(EVENT_SOCKETFACTORY_ERROR, ...errorEvent); 260 | 261 | return; 262 | } 263 | 264 | this.initServerSocket(); 265 | } 266 | 267 | /** 268 | * @throws 269 | */ 270 | protected createServerSocket(): Server { 271 | if (this.config.server?.socketType === SOCKET_WEBSOCKET) { 272 | if (!WSServer) { 273 | throw new Error("WSServer class is not available."); 274 | } 275 | return new WSServer(this.config.server.serverOptions); 276 | } 277 | else if (this.config.server?.socketType === SOCKET_TCP) { 278 | if (!TCPServer) { 279 | throw new Error("TCPServer class is not available."); 280 | } 281 | return new TCPServer(this.config.server.serverOptions); 282 | } 283 | throw new Error("Misconfiguration"); 284 | } 285 | 286 | protected initServerSocket() { 287 | if (!this.serverSocket) { 288 | return; 289 | } 290 | 291 | this.serverSocket.onConnection( async (socket: ClientInterface) => { 292 | const clientIP = socket.getRemoteAddress(); 293 | 294 | if (clientIP) { 295 | if (this.isDenied(clientIP)) { 296 | socket.close(); 297 | 298 | const clientRefuseEvent: Parameters = 299 | [DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_DENIED, clientIP]; 300 | 301 | this.triggerEvent(EVENT_SOCKETFACTORY_CLIENT_IP_REFUSE, ...clientRefuseEvent); 302 | 303 | return; 304 | } 305 | 306 | if (!this.isAllowed(clientIP)) { 307 | socket.close(); 308 | 309 | const clientRefuseEvent: Parameters = 310 | [DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_NOT_ALLOWED, clientIP]; 311 | 312 | this.triggerEvent(EVENT_SOCKETFACTORY_CLIENT_IP_REFUSE, ...clientRefuseEvent); 313 | 314 | return; 315 | } 316 | 317 | if (this.checkConnectionsOverflow(clientIP, true)) { 318 | socket.close(); 319 | 320 | const clientRefuseEvent: Parameters = 321 | [DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_OVERFLOW, clientIP]; 322 | 323 | this.triggerEvent(EVENT_SOCKETFACTORY_CLIENT_IP_REFUSE, ...clientRefuseEvent); 324 | 325 | return; 326 | } 327 | 328 | this.increaseConnectionsCounter(clientIP); 329 | } 330 | socket.onClose( (hadError: boolean) => { 331 | if (clientIP) { 332 | this.decreaseConnectionsCounter(clientIP); 333 | } 334 | 335 | this.serverClientSockets = this.serverClientSockets.filter( (socket2: ClientInterface) => { 336 | return socket !== socket2; 337 | }); 338 | 339 | const closeEvent: Parameters = [socket, true, hadError]; 340 | 341 | this.triggerEvent(EVENT_SOCKETFACTORY_CLOSE, ...closeEvent); 342 | }); 343 | 344 | const connectEvent: Parameters = [socket, true]; 345 | 346 | this.triggerEvent(EVENT_SOCKETFACTORY_CONNECT, ...connectEvent); 347 | }); 348 | this.serverSocket.onError( (errorMessage: string) => { 349 | const error = new Error(errorMessage); 350 | 351 | const serverListenErrorEvent: Parameters = [error]; 352 | 353 | this.triggerEvent(EVENT_SOCKETFACTORY_SERVER_LISTEN_ERROR, 354 | ...serverListenErrorEvent); 355 | 356 | const errorEvent: Parameters = 357 | [EVENT_SOCKETFACTORY_SERVER_LISTEN_ERROR, error as Error]; 358 | 359 | this.triggerEvent(EVENT_SOCKETFACTORY_ERROR, ...errorEvent); 360 | }); 361 | this.serverSocket.listen(); 362 | } 363 | 364 | protected isDenied(key: string): boolean { 365 | const deniedIPs = this.config.server?.deniedIPs || []; 366 | return deniedIPs.includes(key.toLowerCase()); 367 | } 368 | 369 | protected isAllowed(key: string): boolean { 370 | const allowedIPs = this.config.server?.allowedIPs; 371 | if (!allowedIPs) { 372 | return true; 373 | } 374 | return allowedIPs.includes(key.toLowerCase()); 375 | } 376 | 377 | /** 378 | * Increase the counter connections per IP or hostname, 379 | * both for outgoing and incoming connections. 380 | * Note that only when using direct connections on IP address will this be precise, 381 | * when connecting to host names it might not be possible t group that together with 382 | * a connection coming back (since localAddress won't be same as hostname). 383 | */ 384 | protected increaseConnectionsCounter(address: string) { 385 | this.increaseCounter(address); 386 | this.increaseCounter("*"); // "all" counter, always increased. 387 | } 388 | 389 | protected decreaseConnectionsCounter(address: string) { 390 | this.decreaseCounter(address); 391 | this.decreaseCounter("*"); // Always decrease the "all" counter. 392 | } 393 | 394 | /** 395 | * @params key IP address or host name. 396 | * @params isServer set to true if it is server socket checking. 397 | * @returns true if any limit is reached. 398 | */ 399 | protected checkConnectionsOverflow(address: string, isServer: boolean = false): boolean //eslint-disable-line @typescript-eslint/no-unused-vars 400 | { 401 | if (this.config.maxConnections !== undefined) { 402 | const allCount = this.readCounter("*"); 403 | if (allCount >= this.config.maxConnections) { 404 | return true; 405 | } 406 | } 407 | if (this.config.maxConnectionsPerIp !== undefined) { 408 | const ipCount = this.readCounter(address); 409 | if (ipCount >= this.config.maxConnectionsPerIp) { 410 | return true; 411 | } 412 | } 413 | return false; 414 | } 415 | 416 | protected increaseCounter(key: string) { 417 | const obj = this.stats.counters[key] || {counter: 0}; 418 | this.stats.counters[key] = obj; 419 | obj.counter++; 420 | } 421 | 422 | protected decreaseCounter(key: string) { 423 | const obj = this.stats.counters[key]; 424 | if (!obj) { 425 | return; 426 | } 427 | obj.counter--; 428 | } 429 | 430 | protected readCounter(key: string): number { 431 | return (this.stats.counters[key] ?? {}).counter ?? 0; 432 | } 433 | 434 | /** 435 | * Shutdown client and server factories and close all open sockets. 436 | */ 437 | public close() { 438 | if (this._isClosed) { 439 | return; 440 | } 441 | this.shutdown(); 442 | this._isClosed = true; 443 | this.serverClientSockets.forEach( (client: ClientInterface) => { 444 | client.close(); 445 | }); 446 | this.serverClientSockets = []; 447 | this.clientSocket?.close(); 448 | delete this.clientSocket; 449 | } 450 | 451 | /** 452 | * For client factories stop any further connection attempts, but leave existing connection open. 453 | * For server factories close the server socket to not allow any further connections, 454 | * but leave the existing ones open. 455 | */ 456 | public shutdown() { 457 | if (this._isClosed || this._isShutdown) { 458 | return; 459 | } 460 | this._isShutdown = true; 461 | if (this.serverSocket) { 462 | this.serverSocket.close(true); // Only close server, will not close accepted sockets. 463 | delete this.serverSocket; 464 | } 465 | } 466 | 467 | /** 468 | * @returns true if factory is closed. 469 | */ 470 | public isClosed(): boolean { 471 | return this._isClosed; 472 | } 473 | 474 | /** 475 | * @returns true if factory is shutdown. 476 | */ 477 | public isShutdown(): boolean { 478 | return this._isShutdown; 479 | } 480 | 481 | /** 482 | * Get the stats object to be shared with other factories. 483 | * @returns stats 484 | */ 485 | public getStats(): SocketFactoryStats { 486 | return this.stats; 487 | } 488 | 489 | /** 490 | * On any error also this general error event is emitted. 491 | * @param callback 492 | */ 493 | public onSocketFactoryError(callback: SocketFactoryErrorCallback) { 494 | this.hookEvent(EVENT_SOCKETFACTORY_ERROR, callback); 495 | } 496 | 497 | /** 498 | * When the server socket cannot be created. 499 | * @param callback 500 | */ 501 | public onServerInitError(callback: SocketFactoryServerInitErrorCallback) { 502 | this.hookEvent(EVENT_SOCKETFACTORY_SERVER_INIT_ERROR, callback); 503 | } 504 | 505 | /** 506 | * When the server socket cannot bind and listen. 507 | * @param callback 508 | */ 509 | public onServerListenError(callback: SocketFactoryServerListenErrorCallback) { 510 | this.hookEvent(EVENT_SOCKETFACTORY_SERVER_LISTEN_ERROR, callback); 511 | } 512 | 513 | /** 514 | * When the client socket cannot be successfully created, 515 | * likely due to some misconfiguration. 516 | * @param callback 517 | */ 518 | public onClientInitError(callback: SocketFactoryClientInitErrorCallback) { 519 | this.hookEvent(EVENT_SOCKETFACTORY_CLIENT_INIT_ERROR, callback); 520 | } 521 | 522 | /** 523 | * When the client socket cannot connect due to server not there. 524 | * @param callback 525 | */ 526 | public onConnectError(callback: SocketFactoryClientConnectErrorCallback) { 527 | this.hookEvent(EVENT_SOCKETFACTORY_CLIENT_CONNECT_ERROR, callback); 528 | } 529 | 530 | /** 531 | * When client or server accepted socket has connected. 532 | * @param callback 533 | */ 534 | public onConnect(callback: SocketFactoryConnectCallback) { 535 | this.hookEvent(EVENT_SOCKETFACTORY_CONNECT, callback); 536 | } 537 | 538 | /** 539 | * When a client or a server accepted socket is disconnected. 540 | * One could also directly hook onClose on the connected socket. 541 | * @param callback 542 | */ 543 | public onClose(callback: SocketFactoryCloseCallback) { 544 | this.hookEvent(EVENT_SOCKETFACTORY_CLOSE, callback); 545 | } 546 | 547 | /** 548 | * When a server is refusing and closing an incoming socket due to the client IP address. 549 | * @param callback 550 | */ 551 | public onClientIPRefuse(callback: SocketFactoryClientIPRefuseCallback) { 552 | this.hookEvent(EVENT_SOCKETFACTORY_CLIENT_IP_REFUSE, callback); 553 | } 554 | 555 | protected hookEvent(type: string, callback: (...args: any[]) => void) { 556 | const cbs = this.handlers[type] || []; 557 | this.handlers[type] = cbs; 558 | cbs.push(callback); 559 | } 560 | 561 | protected unhookEvent(type: string, callback: (...args: any[]) => void) { 562 | const cbs = (this.handlers[type] || []).filter( (cb: (data?: any[]) => void) => 563 | callback !== cb ); 564 | 565 | this.handlers[type] = cbs; 566 | } 567 | 568 | protected triggerEvent(type: string, ...args: any[]) { 569 | const cbs = this.handlers[type] || []; 570 | cbs.forEach( callback => { 571 | callback(...args); 572 | }); 573 | } 574 | } 575 | -------------------------------------------------------------------------------- /src/TCPClient.ts: -------------------------------------------------------------------------------- 1 | import * as net from "net"; 2 | import * as tls from "tls"; 3 | 4 | import {Client} from "./Client"; 5 | import {ClientOptions} from "./types"; 6 | 7 | /** 8 | * TCP client socket implementation. 9 | */ 10 | export class TCPClient extends Client 11 | { 12 | protected socket?: net.Socket; 13 | 14 | constructor(clientOptions?: ClientOptions, socket?: net.Socket) { 15 | super(clientOptions); 16 | this.socket = socket; 17 | 18 | if (this.socket) { 19 | this.socketHook(); 20 | } 21 | } 22 | 23 | public getSocket(): net.Socket { 24 | if (!this.socket) { 25 | throw new Error("Socket not initiated."); 26 | } 27 | 28 | return this.socket; 29 | } 30 | 31 | /** 32 | * @return {string | undefined} local IP address 33 | */ 34 | public getLocalAddress(): string | undefined { 35 | if (this.socket && typeof this.socket.localAddress === "string") { 36 | return this.socket.localAddress; 37 | } 38 | } 39 | 40 | /** 41 | * @return {string | undefined} remote IP address 42 | */ 43 | public getRemoteAddress(): string | undefined { 44 | if (this.socket && typeof this.socket.remoteAddress === "string") { 45 | return this.socket.remoteAddress; 46 | } 47 | } 48 | 49 | /** 50 | * @return {number | undefined} remote port 51 | */ 52 | public getRemotePort(): number | undefined { 53 | if (this.socket && typeof this.socket.remotePort === "number") { 54 | return this.socket.remotePort; 55 | } 56 | } 57 | 58 | /** 59 | * @return {number | undefined} local port 60 | */ 61 | public getLocalPort(): number | undefined { 62 | if (this.socket && typeof this.socket.localPort === "number") { 63 | return this.socket.localPort; 64 | } 65 | } 66 | 67 | /** 68 | * Specifies how the socket gets initialized and created, then establishes a connection. 69 | */ 70 | protected socketConnect() { 71 | if (this.socket) { 72 | throw new Error("Socket already created."); 73 | } 74 | 75 | if (!this.clientOptions) { 76 | throw new Error("clientOptions is required to create socket."); 77 | } 78 | 79 | const USE_TLS = this.clientOptions.secure ? true: false; 80 | 81 | if(USE_TLS) { 82 | this.socket = tls.connect({ 83 | host: this.clientOptions.host, 84 | port: this.clientOptions.port, 85 | cert: this.clientOptions.cert, 86 | key: this.clientOptions.key, 87 | rejectUnauthorized: this.clientOptions.rejectUnauthorized, 88 | ca: this.clientOptions.ca 89 | }); 90 | if (this.socket) { 91 | this.socket.on("secureConnect", this.socketConnected); 92 | } 93 | } 94 | else { 95 | this.socket = net.connect({ 96 | host: this.clientOptions.host, 97 | port: this.clientOptions.port, 98 | }); 99 | if (this.socket) { 100 | this.socket.on("connect", this.socketConnected); 101 | } 102 | } 103 | 104 | if (!this.socket) { 105 | throw new Error("Could not create socket."); 106 | } 107 | } 108 | 109 | /** 110 | * Specifies hooks to be called as part of the connect procedure. 111 | */ 112 | protected socketHook() { 113 | if (!this.socket) { 114 | return; 115 | } 116 | 117 | this.socket.on("data", this.socketData); // Incoming data 118 | this.socket.on("error", this.error); // Error connecting 119 | this.socket.on("close", this.socketClosed); // Socket closed 120 | } 121 | 122 | protected unhookError() { 123 | this.socket?.off("error", this.error); 124 | } 125 | 126 | /** 127 | * Defines how data gets written to the socket. 128 | * 129 | * @param {data} Buffer or string - data to be sent 130 | */ 131 | protected socketSend(data: Buffer | string) { 132 | if (!this.socket) { 133 | throw new Error("Socket not instantiated"); 134 | } 135 | 136 | if (!Buffer.isBuffer(data)) { 137 | this.socket.write(Buffer.from(data)); 138 | } 139 | else { 140 | this.socket.write(data); 141 | } 142 | } 143 | 144 | /** 145 | * Defines the steps to be performed during close. 146 | */ 147 | protected socketClose() { 148 | if (this.socket) { 149 | this.socket.end(); 150 | } 151 | } 152 | 153 | protected error = (error: Error) => { 154 | this.socketError(error.message || "TCP socket could not connect"); 155 | }; 156 | } 157 | -------------------------------------------------------------------------------- /src/TCPServer.ts: -------------------------------------------------------------------------------- 1 | import * as net from "net"; 2 | import * as tls from "tls"; 3 | 4 | import {Server} from "./Server"; 5 | import {TCPClient} from "./TCPClient"; 6 | import {ServerOptions} from "./types"; 7 | 8 | /** 9 | * TCP server implementation. 10 | */ 11 | export class TCPServer extends Server 12 | { 13 | protected server?: net.Server; 14 | 15 | constructor(serverOptions: ServerOptions) { 16 | super(serverOptions); 17 | this.serverCreate(); 18 | } 19 | 20 | /** 21 | * Specifies how the server gets initialized, then creates the server with the specified options. 22 | */ 23 | protected serverCreate() 24 | { 25 | const USE_TLS = this.serverOptions.cert != null; 26 | 27 | if(USE_TLS) { 28 | const tlsOptions = { 29 | cert: this.serverOptions.cert, 30 | key: this.serverOptions.key, 31 | requestCert: this.serverOptions.requestCert, 32 | rejectUnauthorized: this.serverOptions.rejectUnauthorized, 33 | ca: this.serverOptions.ca, 34 | handshakeTimeout: 30000, 35 | }; 36 | this.server = tls.createServer(tlsOptions); 37 | this.server?.on("secureConnection", this.clientConnected); 38 | } 39 | else { 40 | this.server = net.createServer(); 41 | this.server?.on("connection", this.clientConnected); 42 | } 43 | 44 | this.server?.on("error", this.error); 45 | this.server?.on("close", this.serverClosed); 46 | this.server?.on("tlsClientError", (_err: any, socket: net.Socket) => socket.end()); 47 | } 48 | 49 | /** 50 | * Starts a previously created server listening for connections. 51 | * Assumes the server is instantiated during object creation. 52 | */ 53 | protected serverListen() 54 | { 55 | this.server?.listen({ 56 | host: this.serverOptions.host, 57 | port: this.serverOptions.port, 58 | ipv6Only: this.serverOptions.ipv6Only, 59 | }); 60 | } 61 | 62 | protected serverClose() { 63 | if (this.server) { 64 | this.server.close(); 65 | } 66 | } 67 | 68 | protected clientConnected = (socket: net.Socket) => { 69 | const client = new TCPClient({ 70 | bufferData: this.serverOptions.bufferData, 71 | port: this.serverOptions.port, 72 | textMode: this.serverOptions.textMode, 73 | }, socket); 74 | 75 | this.addClient(client); 76 | } 77 | 78 | protected error = (error: Error) => { 79 | this.serverError(error.message); 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/VirtualClient.ts: -------------------------------------------------------------------------------- 1 | import {Client} from "./Client"; 2 | 3 | /** 4 | * A mock client socket which can be used for simulating regular socket communications. 5 | * This allows to have the same interface when working with sockets but when not needing 6 | * the actual socket because both parties are in the same process. 7 | */ 8 | export class VirtualClient extends Client 9 | { 10 | protected pairedSocket?: VirtualClient; 11 | protected latency: number; 12 | protected outQueue: (Buffer | string)[]; 13 | protected remoteAddress: string | undefined; 14 | protected localAddress: string | undefined; 15 | protected remotePort: number | undefined; 16 | protected localPort: number | undefined; 17 | 18 | /** 19 | * @constructor 20 | * @param {VirtualClient} [pairedSocket] When creating the second socket of a socket-pair provide the first socket as argument to get them paired. 21 | */ 22 | constructor(pairedSocket?: VirtualClient) { 23 | super({port: 0}); 24 | 25 | this.pairedSocket = pairedSocket; 26 | 27 | /** We can set this to simulate some latency in the paired socket communication */ 28 | this.latency = 0; // Milliseconds 29 | 30 | /** 31 | * Queue of outgoing messages. 32 | * We need this if we use simulated latency, 33 | * because the ordering of setTimeout might not be guaranteed 34 | * for identical timeout values. 35 | */ 36 | this.outQueue = []; 37 | 38 | /* Complete the pairing by assigning this socket as our paired socket's paired socket */ 39 | if (this.pairedSocket) { 40 | this.pairedSocket.pairedSocket = this; 41 | } 42 | } 43 | 44 | /** 45 | * Pair this socket with given socket and emit connected events on this socket and then on given socket. 46 | * @param pairedSocket the client to pair this client with. 47 | */ 48 | public pair(pairedSocket: VirtualClient) { 49 | if (this.pairedSocket) { 50 | throw new Error("Socket can only be paired once."); 51 | } 52 | this.pairedSocket = pairedSocket; 53 | this.pairedSocket.pairedSocket = this; 54 | this.socketConnected(); 55 | this.pairedSocket.pairedSocket.socketConnected(); 56 | } 57 | 58 | /** 59 | * Set a simulated latency of the socket communications. 60 | * 61 | * @param {number} latency in milliseconds for each send 62 | */ 63 | public setLatency(latency: number) { 64 | if (latency < this.latency && this.outQueue.length > 0) { 65 | throw new Error("Cannot decrease latency while data is still waiting to send."); 66 | } 67 | this.latency = latency; 68 | } 69 | 70 | public setLocalAddress(localAddress: string | undefined) { 71 | this.localAddress = localAddress; 72 | } 73 | 74 | public setRemoteAddress(remoteAddress: string | undefined) { 75 | this.remoteAddress = remoteAddress; 76 | } 77 | 78 | public setRemotePort(remotePort: number | undefined) { 79 | this.remotePort = remotePort; 80 | } 81 | 82 | public setLocalPort(localPort: number | undefined) { 83 | this.localPort = localPort; 84 | } 85 | 86 | public getLocalAddress(): string | undefined { 87 | return this.localAddress; 88 | } 89 | 90 | public getRemoteAddress(): string | undefined { 91 | return this.remoteAddress; 92 | } 93 | 94 | public getRemotePort(): number | undefined { 95 | return this.remotePort; 96 | } 97 | 98 | public getLocalPort(): number | undefined { 99 | return this.localPort; 100 | } 101 | 102 | /** 103 | * Hook events on the socket. 104 | */ 105 | protected socketHook() { 106 | // Do nothing 107 | // We handle events in different ways since this is not an actual socket. 108 | } 109 | 110 | /** 111 | * Send the given data on socket. 112 | * @param {Buffer | string} data 113 | */ 114 | protected socketSend(data: Buffer | string) { 115 | // Put msg into paired socket. 116 | if (this.pairedSocket) { 117 | this.outQueue.push(data); 118 | if (this.latency > 0) { 119 | setTimeout( () => this.copyToPaired(), this.latency); 120 | } else { 121 | this.copyToPaired(); 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * Specify the paired close procedure. 128 | */ 129 | protected socketClose() { 130 | const hadError = false; 131 | if (this.pairedSocket) { 132 | this.pairedSocket.socketClosed(hadError); 133 | } 134 | this.socketClosed(hadError); 135 | } 136 | 137 | /** 138 | * Internal function to copy one message in the out queue to the paired socket. 139 | * 140 | */ 141 | protected copyToPaired() { 142 | if (this.pairedSocket) { 143 | const data = this.outQueue.shift(); 144 | if (data !== undefined) { 145 | this.pairedSocket.socketData(data); 146 | } 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Create two paired virtual clients. 153 | * @returns tuple of two clients. 154 | */ 155 | export function CreatePair(): [VirtualClient, VirtualClient] { 156 | const socket1 = new VirtualClient(); 157 | const socket2 = new VirtualClient(socket1); 158 | return [socket1, socket2]; 159 | } 160 | -------------------------------------------------------------------------------- /src/VirtualServer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Server, 3 | } from "./Server"; 4 | 5 | import { 6 | VirtualClient, 7 | } from "./VirtualClient"; 8 | 9 | import { 10 | ServerOptions, 11 | } from "./types"; 12 | 13 | /** 14 | * Simulate a server socket listening. 15 | */ 16 | export class VirtualServer extends Server 17 | { 18 | /** 19 | * @param serverOptions are ignored. 20 | */ 21 | constructor(serverOptions: ServerOptions) { 22 | super(serverOptions); 23 | this.serverCreate(); 24 | } 25 | 26 | /** 27 | * Simulate that a client has connected. 28 | * @param client the client which has connected to this server. 29 | * Note that the given client must also be paired with its counterpart. 30 | */ 31 | public simulateConnection(client: VirtualClient) { 32 | this.addClient(client); 33 | } 34 | 35 | /** 36 | * Trigger an error event. 37 | */ 38 | public simulateError(error: Error) { 39 | this.serverError(error.message); 40 | } 41 | 42 | /** 43 | * Specifies how the server gets initialized, then creates the server with the specified options. 44 | */ 45 | protected serverCreate() 46 | { 47 | // Do nothing 48 | } 49 | 50 | protected serverListen() 51 | { 52 | // Do nothing 53 | } 54 | 55 | protected serverClose() { 56 | this.serverClosed(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/WSClient.ts: -------------------------------------------------------------------------------- 1 | // Add browser/browserify/parcel dependency 2 | // @ts-ignore 3 | if(typeof "process" === "undefined" && !process && !process.versions && !process.versions.node) { 4 | require("regenerator-runtime/runtime"); 5 | } 6 | 7 | import {Client} from "./Client"; 8 | import {ClientOptions} from "./types"; 9 | import * as ws from "ws"; 10 | 11 | let isBrowser: boolean; 12 | let WebSocketClass: any = null; 13 | //@ts-ignore 14 | if (typeof WebSocket !== "undefined") { 15 | isBrowser = true; 16 | //@ts-ignore 17 | WebSocketClass = WebSocket; 18 | //@ts-ignore 19 | } else if (typeof MozWebSocket !== "undefined") { 20 | isBrowser = true; 21 | //@ts-ignore 22 | WebSocketClass = MozWebSocket; 23 | //@ts-ignore 24 | } else if (typeof window !== "undefined") { 25 | isBrowser = true; 26 | //@ts-ignore 27 | WebSocketClass = window.WebSocket || window.MozWebSocket; 28 | } else { 29 | isBrowser = false; 30 | // @ts-ignore 31 | WebSocketClass = ws.WebSocket; 32 | } 33 | 34 | /** 35 | * WebSocket client implementation. 36 | */ 37 | export class WSClient extends Client 38 | { 39 | protected socket?: ws; 40 | 41 | constructor(clientOptions?: ClientOptions, socket?: ws) { 42 | super(clientOptions); 43 | this.socket = socket; 44 | 45 | if (this.socket) { 46 | if (isBrowser) { 47 | this.socket.binaryType = "arraybuffer"; 48 | } 49 | 50 | this.socketHook(); 51 | } 52 | } 53 | 54 | public getSocket(): ws { 55 | if (!this.socket) { 56 | throw new Error("Socket not initiated"); 57 | } 58 | 59 | return this.socket; 60 | } 61 | 62 | public isWebSocket(): boolean { 63 | return true; 64 | } 65 | 66 | /** 67 | * Note this does always return undefined in browser. 68 | * @return {string | undefined} local IP address 69 | */ 70 | public getLocalAddress(): string | undefined { 71 | //@ts-ignore 72 | if (this.socket?._socket && typeof this.socket._socket.localAddress === "string") { 73 | //@ts-ignore 74 | return this.socket._socket.localAddress; 75 | } 76 | } 77 | 78 | /** 79 | * Note this does always return undefined in browser. 80 | * @return {string | undefined} remote IP address 81 | */ 82 | public getRemoteAddress(): string | undefined { 83 | //@ts-ignore 84 | if (this.socket?._socket && typeof this.socket._socket.remoteAddress === "string") { 85 | //@ts-ignore 86 | return this.socket._socket.remoteAddress; 87 | } 88 | } 89 | 90 | /** 91 | * Note this does always return undefined in browser. 92 | * @return {number | undefined} remote port 93 | */ 94 | public getRemotePort(): number | undefined { 95 | //@ts-ignore 96 | if (this.socket?._socket && typeof this.socket._socket.remotePort === "number") { 97 | //@ts-ignore 98 | return this.socket._socket.remotePort; 99 | } 100 | } 101 | 102 | /** 103 | * Note this does always return undefined in browser. 104 | * @return {number | undefined} local port 105 | */ 106 | public getLocalPort(): number | undefined { 107 | //@ts-ignore 108 | if (this.socket?._socket && typeof this.socket._socket.localPort === "number") { 109 | //@ts-ignore 110 | return this.socket._socket.localPort; 111 | } 112 | } 113 | 114 | /** 115 | * Specifies how the socket gets initialized and created, then establishes a connection. 116 | */ 117 | protected socketConnect() { 118 | if (this.socket) { 119 | throw new Error("Socket already created"); 120 | } 121 | 122 | if (!this.clientOptions) { 123 | throw new Error("clientOptions is required to create socket"); 124 | } 125 | 126 | let host = this.clientOptions.host ? this.clientOptions.host : "localhost"; 127 | 128 | // 129 | // The following browser-friendly Node.js net module isIPv6 procedure was inspired by the net-browserify package. 130 | // 131 | // References: 132 | // - https://www.npmjs.com/package/net-browserify 133 | // - https://github.com/emersion/net-browserify 134 | const isIPv6 = function(input: string) { 135 | return /^(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))$/.test(input); 136 | }; 137 | // Ensure IPv6 host gets surrounded with brackets for later address formation 138 | if(isIPv6(host)) { 139 | host = `[${host}]`; 140 | } 141 | 142 | const USE_TLS = this.clientOptions.secure ? true: false; 143 | 144 | let address; 145 | if(USE_TLS) { 146 | address = `wss://${host}:${this.clientOptions.port}`; 147 | } 148 | else { 149 | address = `ws://${host}:${this.clientOptions.port}`; 150 | } 151 | 152 | if(isBrowser) { 153 | this.socket = new WebSocketClass(address); 154 | // Make sure binary type is set to ArrayBuffer instead of Blob 155 | if (this.socket) { 156 | this.socket.binaryType = "arraybuffer"; 157 | } 158 | } else { 159 | this.socket = new WebSocketClass(address, { 160 | cert: this.clientOptions.cert, 161 | key: this.clientOptions.key, 162 | rejectUnauthorized: this.clientOptions.rejectUnauthorized, 163 | ca: this.clientOptions.ca, 164 | perMessageDeflate: false, 165 | maxPayload: 100 * 1024 * 1024, 166 | }); 167 | } 168 | 169 | if (this.socket) { 170 | this.socket.onopen = this.socketConnected; 171 | } 172 | else { 173 | throw new Error("Could not create socket"); 174 | } 175 | } 176 | 177 | /** 178 | * Specifies hooks to be called as part of the connect procedure. 179 | */ 180 | protected socketHook() { 181 | if (!this.socket) { 182 | return; 183 | } 184 | 185 | this.socket.onmessage = (msg: any) => { 186 | let data = msg.data; 187 | 188 | // Under Browser settings, convert message data from ArrayBuffer to Buffer. 189 | if (isBrowser) { 190 | if (typeof(data) !== "string") { 191 | const bytes = new Uint8Array(data); 192 | data = Buffer.from(bytes); 193 | } 194 | } 195 | 196 | this.socketData(data); 197 | }; 198 | this.socket.onerror = this.error; // Error connecting 199 | this.socket.onclose = (closeEvent) => this.socketClosed(closeEvent && closeEvent.code === 1000); // Socket closed 200 | } 201 | 202 | protected unhookError() { 203 | if (this.socket) { 204 | this.socket.onerror = null; 205 | } 206 | } 207 | 208 | /** 209 | * Defines how data gets written to the socket. 210 | * @param {data} buffer or string - data to be sent 211 | * strings are sent as UTF-8 text, Buffers as binary. 212 | * @throws if socket not instantiated. 213 | */ 214 | protected socketSend(data: Buffer | string) { 215 | if (!this.socket) { 216 | throw new Error("Socket not instantiated"); 217 | } 218 | 219 | if (this.isTextMode()) { 220 | if (typeof(data) !== "string") { 221 | data = data.toString(); 222 | } 223 | 224 | this.socket.send(data, {binary: false, compress: false}); 225 | } 226 | else { 227 | if (!Buffer.isBuffer(data)) { 228 | data = Buffer.from(data); 229 | } 230 | 231 | this.socket.send(data, {binary: true, compress: false}); 232 | } 233 | } 234 | 235 | /** 236 | * Defines the steps to be performed during close. 237 | */ 238 | protected socketClose() { 239 | if (this.socket) { 240 | this.socket.close(); 241 | } 242 | } 243 | 244 | protected error = (error: ws.ErrorEvent) => { 245 | this.socketError(error.message || error.error || "WebSocket could not connect"); 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /src/WSServer.ts: -------------------------------------------------------------------------------- 1 | import {Server} from "./Server"; 2 | import {WSClient} from "./WSClient"; 3 | import {ServerOptions} from "./types"; 4 | import * as WebSocket from "ws"; 5 | import * as http from "http"; 6 | import * as https from "https"; 7 | 8 | /** 9 | * WebSocket server implementation. 10 | */ 11 | export class WSServer extends Server 12 | { 13 | protected server?: http.Server | https.Server; 14 | protected wsServer?: WebSocket.Server; 15 | 16 | constructor(serverOptions: ServerOptions) { 17 | super(serverOptions); 18 | this.serverCreate(); 19 | } 20 | 21 | /** 22 | * Specifies how the server gets initialized, then creates the server with the specified options. 23 | */ 24 | protected serverCreate() 25 | { 26 | const USE_TLS = this.serverOptions.cert != null; 27 | 28 | if(USE_TLS) { 29 | const tlsOptions = { 30 | cert: this.serverOptions.cert, 31 | key: this.serverOptions.key, 32 | requestCert: this.serverOptions.requestCert, 33 | rejectUnauthorized: this.serverOptions.rejectUnauthorized, 34 | ca: this.serverOptions.ca, 35 | handshakeTimeout: 30000, 36 | }; 37 | this.server = https.createServer(tlsOptions); 38 | if (this.server) { 39 | this.server.on("tlsClientError", this.error); 40 | } 41 | } 42 | else { 43 | this.server = http.createServer(); 44 | } 45 | } 46 | 47 | /** 48 | * Starts a previously created server listening for connections. 49 | * Assumes the server is instantiated during object creation. 50 | */ 51 | protected serverListen() { 52 | this.wsServer = new WebSocket.Server({ 53 | path: "/", 54 | server: this.server, 55 | clientTracking: true, 56 | perMessageDeflate: false, 57 | maxPayload: 100 * 1024 * 1024, 58 | }); 59 | 60 | this.wsServer.on("connection", this.clientConnected); 61 | this.wsServer.on("error", this.error); 62 | this.wsServer.on("close", this.serverClosed); 63 | 64 | this.server?.listen({ 65 | host: this.serverOptions.host, 66 | port: this.serverOptions.port, 67 | ipv6Only: this.serverOptions.ipv6Only, 68 | }); 69 | } 70 | 71 | /** 72 | * Overrides server close procedure. 73 | */ 74 | protected serverClose() { 75 | if (this.wsServer) { 76 | this.wsServer.close(); 77 | } 78 | if (this.server) { 79 | this.server.close(); 80 | } 81 | } 82 | 83 | protected clientConnected = (socket: WebSocket) => { 84 | const client = new WSClient({ 85 | bufferData: this.serverOptions.bufferData, 86 | port: this.serverOptions.port, 87 | textMode: this.serverOptions.textMode, 88 | }, socket); 89 | 90 | this.addClient(client); 91 | } 92 | 93 | protected error = (error: Error) => { 94 | this.serverError(error.message); 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/WrappedClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClientInterface, 3 | WrappedClientInterface, 4 | SocketErrorCallback, 5 | SocketDataCallback, 6 | SocketConnectCallback, 7 | SocketCloseCallback, 8 | } from "./types"; 9 | 10 | /** 11 | * The WrappedClient wraps an already connected client and is a base class to extend where 12 | * specific methods can be overriden to transform incoming and outgoint socket data. 13 | */ 14 | export class WrappedClient implements WrappedClientInterface { 15 | constructor(protected client: ClientInterface) {} 16 | 17 | public getClient(): ClientInterface { 18 | return this.client; 19 | } 20 | 21 | public async init() { 22 | } 23 | 24 | public getSocket(): any { 25 | return this.client.getSocket(); 26 | } 27 | 28 | public isWebSocket(): boolean { 29 | return this.client.isWebSocket(); 30 | } 31 | 32 | public isTextMode(): boolean { 33 | return this.client.isTextMode(); 34 | } 35 | 36 | public connect() { 37 | throw new Error("The WrappedClient's underlaying socket must already have been connected"); 38 | } 39 | 40 | public unRead(data: Buffer | string) { 41 | this.client.unRead(data); 42 | } 43 | 44 | public close() { 45 | this.client.close(); 46 | } 47 | 48 | public isClosed(): boolean { 49 | return this.client.isClosed(); 50 | } 51 | 52 | public send(data: Buffer | string) { 53 | this.client.send(data); 54 | } 55 | 56 | public onError(fn: SocketErrorCallback) { 57 | this.client.onError(fn); 58 | } 59 | 60 | public offError(fn: SocketErrorCallback) { 61 | this.client.offError(fn); 62 | } 63 | 64 | public onData(fn: SocketDataCallback) { 65 | this.client.onData(fn); 66 | } 67 | 68 | public offData(fn: SocketDataCallback) { 69 | this.client.offData(fn); 70 | } 71 | 72 | public onConnect(fn: SocketConnectCallback) { 73 | this.client.onConnect(fn); 74 | } 75 | 76 | public offConnect(fn: SocketConnectCallback) { 77 | this.client.offConnect(fn); 78 | } 79 | 80 | public onClose(fn: SocketCloseCallback) { 81 | this.client.onClose(fn); 82 | } 83 | 84 | public offClose(fn: SocketCloseCallback) { 85 | this.client.offClose(fn); 86 | } 87 | 88 | public getLocalAddress(): string | undefined { 89 | return this.client.getLocalAddress(); 90 | } 91 | 92 | public getRemoteAddress(): string | undefined { 93 | return this.client.getRemoteAddress(); 94 | } 95 | 96 | public getRemotePort(): number | undefined { 97 | return this.client.getRemotePort(); 98 | } 99 | 100 | public getLocalPort(): number | undefined { 101 | return this.client.getLocalPort(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./TCPClient"; 2 | export * from "./TCPServer"; 3 | export * from "./VirtualClient"; 4 | export * from "./VirtualServer"; 5 | export * from "./WSClient"; 6 | export * from "./WSServer"; 7 | export * from "./Client"; 8 | export * from "./Server"; 9 | export * from "./ByteSize"; 10 | export * from "./SocketFactory"; 11 | export * from "./WrappedClient"; 12 | export * from "./types"; 13 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export const SOCKET_WEBSOCKET = "WebSocket"; 2 | export const SOCKET_TCP = "TCP"; 3 | 4 | /** 5 | * Event emitted on client socket connect error. 6 | * @param message potential error message 7 | */ 8 | export type SocketErrorCallback = (message: string) => void; 9 | 10 | /** 11 | * Event emitted on incoming binary or text data on client socket. 12 | * @param data incoming data as Buffer or text (depending on socket configuration) 13 | */ 14 | export type SocketDataCallback = (data: Buffer | string) => void; 15 | 16 | /** 17 | * Event emitted on client socket connect. 18 | */ 19 | export type SocketConnectCallback = () => void; 20 | 21 | /** 22 | * Event emitted on client socket close. 23 | * @param hadError set to true if the socket was closed due to an error. 24 | */ 25 | export type SocketCloseCallback = (hadError: boolean) => void; 26 | 27 | /** 28 | * Event emitted on server socket accepted. 29 | * @param client the newly accepted and created client socket 30 | */ 31 | export type SocketAcceptCallback = (client: ClientInterface) => void; 32 | 33 | export const EVENT_SOCKETFACTORY_ERROR = "ERROR"; 34 | export const EVENT_SOCKETFACTORY_CLOSE = "CLOSE"; 35 | export const EVENT_SOCKETFACTORY_CONNECT = "CONNECT"; 36 | export const EVENT_SOCKETFACTORY_CLIENT_INIT_ERROR = "CLIENT_INIT_ERROR"; 37 | export const EVENT_SOCKETFACTORY_CLIENT_CONNECT_ERROR = "CLIENT_CONNECT_ERROR"; 38 | export const EVENT_SOCKETFACTORY_CLIENT_IP_REFUSE = "CLIENT_IP_REFUSE"; 39 | export const EVENT_SOCKETFACTORY_SERVER_INIT_ERROR = "SERVER_INIT_ERROR"; 40 | export const EVENT_SOCKETFACTORY_SERVER_LISTEN_ERROR = "SERVER_LISTEN_ERROR"; 41 | 42 | export const DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_DENIED = "IP_DENIED"; 43 | export const DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_NOT_ALLOWED = "IP_NOT_ALLOWED"; 44 | export const DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_OVERFLOW = "IP_OVERFLOW"; 45 | 46 | /** Event emitted when client socket cannot be initaited, likely due to misconfiguration. */ 47 | export type SocketFactoryClientInitErrorCallback = (error: Error) => void; 48 | 49 | /** Event emitted when client socket cannot connect to server. */ 50 | export type SocketFactoryClientConnectErrorCallback = (error: Error) => void; 51 | 52 | /** Event emitted when client socket connected. */ 53 | export type SocketFactoryConnectCallback = (client: ClientInterface, isServer: boolean) => void; 54 | 55 | /** Event emitted when either client or server accepted socket is closed. */ 56 | export type SocketFactoryCloseCallback = (client: ClientInterface, isServer: boolean, hadError: boolean) => void; 57 | 58 | /** Event emitted when server socket cannot be created. */ 59 | export type SocketFactoryServerInitErrorCallback = (error: Error) => void; 60 | 61 | /** Event emitted when server socket cannot bind and listen, could be that port is taken. */ 62 | export type SocketFactoryServerListenErrorCallback = (error: Error) => void; 63 | 64 | export type SocketFactoryClientIPRefuseDetail = 65 | typeof DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_DENIED | 66 | typeof DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_NOT_ALLOWED | 67 | typeof DETAIL_SOCKETFACTORY_CLIENT_REFUSE_ERROR_IP_OVERFLOW; 68 | 69 | /** 70 | * Event emitted when server actively refused the client's IP address for a specific reason. 71 | * @param detail the reason why the IP address got refused to connect 72 | * @param ipAddress the textual IP address getting refused 73 | */ 74 | export type SocketFactoryClientIPRefuseCallback = (detail: SocketFactoryClientIPRefuseDetail, ipAddress: string) => void; 75 | 76 | export type SocketFactoryErrorCallbackNames = 77 | typeof EVENT_SOCKETFACTORY_CLIENT_INIT_ERROR | 78 | typeof EVENT_SOCKETFACTORY_CLIENT_CONNECT_ERROR | 79 | typeof EVENT_SOCKETFACTORY_SERVER_INIT_ERROR | 80 | typeof EVENT_SOCKETFACTORY_SERVER_LISTEN_ERROR; 81 | 82 | /** 83 | * The error event is always emitted in addition to every specific error event in the SocketFactory. 84 | * It works as a catch-all error event handler. 85 | * 86 | * @param name is the name of the specific error event which was emitted 87 | * @param error is the original event error argument 88 | */ 89 | export type SocketFactoryErrorCallback = 90 | (name: SocketFactoryErrorCallbackNames, error: Error) => void; 91 | 92 | export interface ClientInterface { 93 | init(): Promise; 94 | connect(): void; 95 | send(data: Buffer | string): void; 96 | close(): void; 97 | isClosed(): boolean; 98 | getSocket(): any; 99 | isWebSocket(): boolean; 100 | isTextMode(): boolean; 101 | onError(fn: SocketErrorCallback): void; 102 | offError(fn: SocketErrorCallback): void; 103 | onData(fn: SocketDataCallback): void; 104 | offData(fn: SocketDataCallback): void; 105 | onConnect(fn: SocketConnectCallback): void; 106 | offConnect(fn: SocketConnectCallback): void; 107 | onClose(fn: SocketCloseCallback): void; 108 | offClose(fn: SocketCloseCallback): void; 109 | getLocalAddress(): string | undefined; 110 | getRemoteAddress(): string | undefined; 111 | getRemotePort(): number | undefined; 112 | getLocalPort(): number | undefined; 113 | unRead(data: Buffer | string): void; 114 | } 115 | 116 | export interface SocketFactoryInterface { 117 | init(): void; 118 | getSocketFactoryConfig(): SocketFactoryConfig; 119 | close(): void; 120 | shutdown(): void; 121 | isClosed(): boolean; 122 | isShutdown(): boolean; 123 | getStats(): SocketFactoryStats; 124 | 125 | /** Catch-all error handler for SocketFactory. */ 126 | onSocketFactoryError(callback: SocketFactoryErrorCallback): void; 127 | 128 | onServerInitError(callback: SocketFactoryServerInitErrorCallback): void; 129 | onServerListenError(callback: SocketFactoryServerListenErrorCallback): void; 130 | onClientInitError(callback: SocketFactoryClientInitErrorCallback): void; 131 | onConnectError(callback: SocketFactoryClientConnectErrorCallback): void; 132 | onConnect(callback: SocketFactoryConnectCallback): void; 133 | onClose(callback: SocketFactoryCloseCallback): void; 134 | 135 | /** Event called when connecting peer has been refused. */ 136 | onClientIPRefuse(callback: SocketFactoryClientIPRefuseCallback): void; 137 | } 138 | 139 | export interface WrappedClientInterface extends ClientInterface { 140 | getClient(): ClientInterface; 141 | } 142 | 143 | export type ClientOptions = { 144 | /** 145 | * RFC6066 states that this should not be an IP address but a name when using TLS. 146 | * Default is "localhost". 147 | */ 148 | host?: string, 149 | 150 | /** 151 | * TCP port number to connect to. 152 | */ 153 | port: number, 154 | 155 | /** 156 | * Set to true (default) to buffer incoming data until an onData is hooked, 157 | * then the buffered data will be fed to that onData handler. 158 | * This can be useful when needing to pass the socket to some other part 159 | * of the code. The passing code does a socket.offData(...) and incoming 160 | * data gets buffered until the new owner does socket.onData(...). 161 | */ 162 | bufferData?: boolean, 163 | 164 | /** 165 | * Set to true to have the socket in text mode (default is false). 166 | * 167 | * If set and data is received on TCP socket that data is translated into text and emitted 168 | * as string on onData() handler. 169 | * 170 | * If set and binary data is received on WebSocket that data translated into text and emitted 171 | * as string on onData() handler. 172 | * 173 | * If set and textual data is received on WebSocket that data is not transformed but emitted 174 | * as it is as string on onData() handler. 175 | * 176 | * 177 | * When not in text mode, data received on TCP socket is emitted as Buffer on the 178 | * onData() handler. 179 | * 180 | * When not in text mode and data received on WebSocket is binary, the data is emitted as Buffer 181 | * on the onData() handler. 182 | * 183 | * When not in text mode and data received on WebSocket is text, the data is transformed into 184 | * Buffer and emitted on the onData() handler. 185 | */ 186 | textMode?: boolean, 187 | 188 | /** 189 | * Set to true to connect over TLS. 190 | */ 191 | secure?: boolean, 192 | 193 | /** 194 | * If true (default) the client will reject any server certificate which is 195 | * not approved by the trusted or the supplied list of CAs in the ca property. 196 | */ 197 | rejectUnauthorized?: boolean, 198 | 199 | /** 200 | * The client certificate needed if the server requires it. 201 | * Cert chains in PEM format. 202 | * Required if server is requiring it. 203 | */ 204 | cert?: string | string[] | Buffer | Buffer[], 205 | 206 | /** 207 | * Client private key in PEM format. 208 | * Required if cert is set. 209 | * Note that the type string[] is not supported. 210 | */ 211 | key?: string | Buffer | Buffer[], 212 | 213 | /** 214 | * Optionally override the trusted CAs, useful for trusting 215 | * server self signed certificates. 216 | */ 217 | ca?: string | string[] | Buffer | Buffer[], 218 | }; 219 | 220 | export type ServerOptions = { 221 | /** 222 | * Host to bind to. 223 | * RFC6066 states that this should not be an IP address but a name when using TLS. 224 | */ 225 | host?: string, 226 | 227 | /** 228 | * TCP port number to listen to. 229 | * Listening to port 0 is not allowed. 230 | * Required. 231 | */ 232 | port: number, 233 | 234 | /** 235 | * Set to true (default) to buffer incoming data until an onData is hooked, 236 | * then the buffered data will be fed to that onData handler. 237 | * This can be useful when needing to pass the socket to some other part 238 | * of the code. The passing code does a socket.offData(...) and incoming 239 | * data gets buffered until the new owner does socket.onData(...). 240 | * This parameter applies to the client sockets accepted on new connections. 241 | */ 242 | bufferData?: boolean, 243 | 244 | /** 245 | * If set then set it for all accepted sockets. 246 | * See ClientOptions.textMode for details. 247 | */ 248 | textMode?: boolean, 249 | 250 | /** 251 | * Set to true to only listen to IPv6 addresses. 252 | */ 253 | ipv6Only?: boolean, 254 | 255 | /* 256 | * If set to true the server will request a client certificate and attempt 257 | * to verify that certificate (see also rejectUnauthorized). 258 | * Default is false. 259 | */ 260 | requestCert?: boolean, 261 | 262 | /** 263 | * If true (default) the server will reject any client certificate which is 264 | * not approved by the trusted or the supplied list of CAs in the ca property. 265 | * This option only has effect if requestCert is set to true. 266 | */ 267 | rejectUnauthorized?: boolean, 268 | 269 | /** 270 | * The server certificate. 271 | * Cert chains in PEM format, one per server private key provided. 272 | * Required if wanting to use TLS. 273 | */ 274 | cert?: string | string[] | Buffer | Buffer[], 275 | 276 | /** 277 | * Server private keys in PEM format. 278 | * Required if cert is set. 279 | * Note that the type string[] is not supported. 280 | */ 281 | key?: string | Buffer | Buffer[], 282 | 283 | /** 284 | * Optionally override the trusted CAs, useful for trusting 285 | * client self signed certificates. 286 | */ 287 | ca?: string | string[] | Buffer | Buffer[], 288 | }; 289 | 290 | export type SocketFactoryStats = { 291 | counters: {[key: string]: {counter: number}}, 292 | }; 293 | 294 | export type SocketFactoryConfig = { 295 | /** 296 | * This must be set if factory is to connect as a client. 297 | * Both client and server can bet set together. 298 | */ 299 | client?: { 300 | socketType: typeof SOCKET_WEBSOCKET | typeof SOCKET_TCP, 301 | 302 | clientOptions: ClientOptions, 303 | 304 | /** If set greater than 0 wait as many seconds to reconnect a closed client. */ 305 | reconnectDelay?: number, 306 | }, 307 | 308 | /** 309 | * This must be set if factory is to connect as a server. 310 | * Both client and server can bet set together. 311 | */ 312 | server?: { 313 | socketType: typeof SOCKET_WEBSOCKET | typeof SOCKET_TCP, 314 | 315 | serverOptions: ServerOptions, 316 | 317 | /** If client IP is in the array then the connection will be disallowed and disconnected. Lowercase strings are required. */ 318 | deniedIPs: string[], 319 | 320 | /** If set then the client IP must be within the array to be allowed to connect. Lowercase strings are required. */ 321 | allowedIPs?: string[], 322 | }, 323 | 324 | /** Total allowed number of socket connections for the factory. */ 325 | maxConnections?: number, 326 | 327 | /** 328 | * Max connections per remote IP for the factory. 329 | * Note that when sharing this state between server and client sockets 330 | * it is important that the client `host` field is the remote IP address, 331 | * the same IP address as read from the socket when server is accepting it. 332 | * If the client is using a hostname as `host` then the shared counting will not be correct, 333 | * since hostname and clientIP will not match. 334 | */ 335 | maxConnectionsPerIp?: number, 336 | }; 337 | -------------------------------------------------------------------------------- /test/Server.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {Server, Client} from "../index"; 3 | 4 | const assert = require("assert"); 5 | 6 | @TestSuite() 7 | export class ServerConstructor { 8 | 9 | @Test() 10 | public valid_options() { 11 | class TestServer extends Server { 12 | _serverCreate() { 13 | } 14 | } 15 | 16 | assert.doesNotThrow(() => { 17 | const server = new TestServer({ 18 | "host": "host.com", 19 | "port": 99, 20 | }); 21 | 22 | //@ts-ignore: protected data 23 | assert(server.serverOptions.host == "host.com"); 24 | //@ts-ignore: protected data 25 | assert(server.serverOptions.port == 99); 26 | //@ts-ignore: protected data 27 | assert(server.serverOptions.rejectUnauthorized == null); 28 | //@ts-ignore: protected data 29 | assert(server.serverOptions.bufferData == undefined); 30 | //@ts-ignore: protected data 31 | assert(server.serverOptions.ipv6Only == undefined); 32 | //@ts-ignore: protected data 33 | assert(server.serverOptions.requestCert == undefined); 34 | //@ts-ignore: protected data 35 | assert(server.serverOptions.cert == undefined); 36 | //@ts-ignore: protected data 37 | assert(server.serverOptions.key == undefined); 38 | //@ts-ignore: protected data 39 | assert(server.serverOptions.ca == undefined); 40 | //@ts-ignore: protected data 41 | assert(server.clients.length == 0); 42 | //@ts-ignore: protected data 43 | assert(Object.keys(server.eventHandlers).length == 0); 44 | //@ts-ignore: protected data 45 | assert(server._isClosed == false); 46 | }); 47 | } 48 | 49 | public server_create_triggered() { 50 | let flag = false; 51 | class TestServer extends Server { 52 | _serverCreate() { 53 | flag = true; 54 | } 55 | } 56 | 57 | assert.doesNotThrow(() => { 58 | assert(flag == false); 59 | const server = new TestServer({ 60 | "host": "host.com", 61 | "port": 99, 62 | }); 63 | assert(flag == true); 64 | assert(server); 65 | }); 66 | } 67 | } 68 | 69 | @TestSuite() 70 | export class ServerServerClose { 71 | 72 | @Test() 73 | public trigger_server_close() { 74 | let flag = false; 75 | class TestServer extends Server { 76 | _serverCreate() { 77 | } 78 | serverClose() { 79 | flag = true; 80 | } 81 | } 82 | 83 | assert.doesNotThrow(() => { 84 | const server = new TestServer({ 85 | "host": "host.com", 86 | "port": 99, 87 | }); 88 | assert(flag == false); 89 | //@ts-ignore 90 | server.serverClose(); 91 | assert(flag == true); 92 | }); 93 | } 94 | } 95 | 96 | @TestSuite() 97 | export class ServerListen { 98 | 99 | @Test() 100 | public trigger_server_listen() { 101 | let flag = false; 102 | class TestServer extends Server { 103 | _serverCreate() { 104 | } 105 | serverListen() { 106 | flag = true; 107 | } 108 | } 109 | 110 | assert.doesNotThrow(() => { 111 | const server = new TestServer({ 112 | "host": "host.com", 113 | "port": 99, 114 | }); 115 | assert(flag == false); 116 | server.listen(); 117 | assert(flag == true); 118 | }); 119 | } 120 | } 121 | 122 | @TestSuite() 123 | export class ServerClose { 124 | 125 | @Test() 126 | public call_server_close() { 127 | let flag = false; 128 | class TestServer extends Server { 129 | _serverCreate() { 130 | } 131 | serverClose() { 132 | flag = true; 133 | } 134 | } 135 | 136 | assert.doesNotThrow(() => { 137 | const server = new TestServer({ 138 | "host": "host.com", 139 | "port": 99, 140 | }); 141 | assert(flag == false); 142 | server.close(); 143 | assert(flag == true); 144 | }); 145 | } 146 | 147 | @Test() 148 | public call_clients_close() { 149 | class TestServer extends Server { 150 | _serverCreate() { 151 | } 152 | serverClose() { 153 | } 154 | } 155 | 156 | let clientCloseCounter = 0; 157 | class MyClient extends Client { 158 | close() { 159 | clientCloseCounter++; 160 | } 161 | } 162 | 163 | assert.doesNotThrow(() => { 164 | const server = new TestServer({ 165 | "host": "host.com", 166 | "port": 99, 167 | }); 168 | 169 | //@ts-ignore: protected data 170 | server.clients.push(new MyClient({port: 1})); 171 | //@ts-ignore: protected data 172 | server.clients.push(new MyClient({port: 2})); 173 | //@ts-ignore: protected data 174 | server.clients.push(new MyClient({port: 3})); 175 | 176 | //@ts-ignore: protected data 177 | assert(server.clients.length == 3); 178 | assert(clientCloseCounter == 0); 179 | server.close(); 180 | //@ts-ignore: protected data 181 | assert(server.clients.length == 0); 182 | assert(clientCloseCounter == 3); 183 | }); 184 | } 185 | } 186 | 187 | @TestSuite() 188 | export class ServerOnConnection { 189 | 190 | @Test() 191 | public trigger_connection_callback() { 192 | let flag = false; 193 | //@ts-ignore 194 | class TestServer extends Server { 195 | _serverCreate() { 196 | } 197 | on(evt: string, fn: Function) { 198 | assert(evt == "connection"); 199 | assert(fn instanceof Function); 200 | } 201 | } 202 | 203 | assert.doesNotThrow(() => { 204 | assert(flag == false); 205 | const server = new TestServer({ 206 | "host": "host.com", 207 | "port": 99, 208 | }); 209 | server.onConnection(function(){}); 210 | }); 211 | } 212 | } 213 | 214 | @TestSuite() 215 | export class ServerOnError { 216 | 217 | @Test() 218 | public trigger_error_callback() { 219 | let flag = false; 220 | //@ts-ignore 221 | class TestServer extends Server { 222 | _serverCreate() { 223 | } 224 | on(evt: string, fn: Function) { 225 | assert(evt == "error"); 226 | assert(fn instanceof Function); 227 | } 228 | } 229 | 230 | assert.doesNotThrow(() => { 231 | assert(flag == false); 232 | const server = new TestServer({ 233 | "host": "host.com", 234 | "port": 99, 235 | }); 236 | server.onError(function(){}); 237 | }); 238 | } 239 | } 240 | 241 | @TestSuite() 242 | export class ServerOnClose { 243 | 244 | @Test() 245 | public trigger_close_callback() { 246 | let flag = false; 247 | //@ts-ignore 248 | class TestServer extends Server { 249 | _serverCreate() { 250 | } 251 | on(evt: string, fn: Function) { 252 | assert(evt == "close"); 253 | assert(fn instanceof Function); 254 | } 255 | } 256 | 257 | assert.doesNotThrow(() => { 258 | assert(flag == false); 259 | const server = new TestServer({ 260 | "host": "host.com", 261 | "port": 99, 262 | }); 263 | server.onClose(function(){}); 264 | }); 265 | } 266 | } 267 | 268 | @TestSuite() 269 | export class ServerAddClient { 270 | 271 | @Test() 272 | public successful_call() { 273 | let flag = false; 274 | //@ts-ignore 275 | class TestServer extends Server { 276 | _serverCreate() { 277 | } 278 | triggerEvent(evt: string) { 279 | assert(evt == "connection"); 280 | flag = true; 281 | } 282 | } 283 | 284 | let clientOnCloseCounter = 0; 285 | class Client { 286 | onClose() { 287 | clientOnCloseCounter++; 288 | } 289 | } 290 | 291 | assert.doesNotThrow(() => { 292 | const server = new TestServer({ 293 | "host": "host.com", 294 | "port": 99, 295 | }); 296 | assert(flag == false); 297 | //@ts-ignore: protected data 298 | assert(server.clients.length == 0); 299 | assert(clientOnCloseCounter == 0); 300 | //@ts-ignore 301 | server.addClient(new Client()); 302 | assert(flag == true); 303 | //@ts-ignore: protected data 304 | assert(server.clients.length == 1); 305 | assert(clientOnCloseCounter == 1); 306 | }); 307 | } 308 | } 309 | 310 | @TestSuite() 311 | export class ServerRemoveClient { 312 | 313 | @Test() 314 | public successful_call() { 315 | //@ts-ignore 316 | class TestServer extends Server { 317 | _serverCreate() { 318 | } 319 | triggerEvent(evt: string) { 320 | } 321 | } 322 | class Client { 323 | onClose() { 324 | } 325 | } 326 | assert.doesNotThrow(() => { 327 | const server = new TestServer({ 328 | "host": "host.com", 329 | "port": 99, 330 | }); 331 | //@ts-ignore: protected data 332 | assert(server.clients.length == 0); 333 | const client = new Client(); 334 | //@ts-ignore 335 | server.addClient(client); 336 | //@ts-ignore: protected data 337 | assert(server.clients.length == 1); 338 | //@ts-ignore 339 | server.removeClient(client); 340 | //@ts-ignore: protected data 341 | assert(server.clients.length == 0); 342 | }); 343 | } 344 | } 345 | 346 | @TestSuite() 347 | export class ServerError { 348 | 349 | @Test() 350 | public successful_call() { 351 | let flag = false; 352 | //@ts-ignore 353 | class TestServer extends Server { 354 | _serverCreate() { 355 | } 356 | triggerEvent(evt: string) { 357 | assert(evt == "error"); 358 | flag = true; 359 | } 360 | } 361 | 362 | assert.doesNotThrow(() => { 363 | const server = new TestServer({ 364 | "host": "host.com", 365 | "port": 99, 366 | }); 367 | assert(flag == false); 368 | //@ts-ignore 369 | server.serverError("msg"); 370 | assert(flag == true); 371 | }); 372 | } 373 | } 374 | 375 | @TestSuite() 376 | export class ServerCloseInner { 377 | 378 | @Test() 379 | public successful_call() { 380 | let flag = false; 381 | //@ts-ignore 382 | class TestServer extends Server { 383 | _serverCreate() { 384 | } 385 | triggerEvent(evt: string) { 386 | assert(evt == "close"); 387 | flag = true; 388 | } 389 | } 390 | 391 | assert.doesNotThrow(() => { 392 | const server = new TestServer({ 393 | "host": "host.com", 394 | "port": 99, 395 | }); 396 | assert(flag == false); 397 | //@ts-ignore 398 | server.serverClosed(); 399 | assert(flag == true); 400 | }); 401 | } 402 | } 403 | 404 | @TestSuite() 405 | export class ServerOn { 406 | 407 | @Test() 408 | public successful_call() { 409 | class TestServer extends Server { 410 | _serverCreate() { 411 | } 412 | _socketHook() { 413 | } 414 | } 415 | 416 | assert.doesNotThrow(() => { 417 | const server = new TestServer({ 418 | "host": "host.com", 419 | "port": 99, 420 | }); 421 | //@ts-ignore: protected data 422 | assert(!server.eventHandlers["myevent"]); 423 | //@ts-ignore 424 | server.on("myevent", function(){}); 425 | //@ts-ignore: protected data 426 | assert(server.eventHandlers["myevent"]); 427 | }); 428 | } 429 | } 430 | 431 | @TestSuite() 432 | export class ServerValidateConfig { 433 | 434 | @Test() 435 | public all_options() { 436 | assert.doesNotThrow(() => { 437 | class TestServer extends Server { 438 | _serverCreate() { 439 | } 440 | } 441 | 442 | const server = new TestServer({ 443 | "host": "host.com", 444 | "port": 99, 445 | "ipv6Only": false, 446 | "requestCert": true, 447 | "cert": "mycert", 448 | "key": "mykey", 449 | "ca": "myca", 450 | }); 451 | 452 | //@ts-ignore: protected data 453 | assert(server.serverOptions.host == "host.com"); 454 | //@ts-ignore: protected data 455 | assert(server.serverOptions.port == 99); 456 | //@ts-ignore: protected data 457 | assert(server.serverOptions.ipv6Only == false); 458 | //@ts-ignore: protected data 459 | assert(server.serverOptions.rejectUnauthorized == null); 460 | //@ts-ignore: protected data 461 | assert(server.serverOptions.requestCert == true); 462 | //@ts-ignore: protected data 463 | assert(server.serverOptions.cert == "mycert"); 464 | //@ts-ignore: protected data 465 | assert(server.serverOptions.key == "mykey"); 466 | //@ts-ignore: protected data 467 | assert(server.serverOptions.ca == "myca"); 468 | }); 469 | } 470 | } 471 | 472 | @TestSuite() 473 | export class ServerTriggerEvent { 474 | 475 | @Test() 476 | public all_options() { 477 | assert.doesNotThrow(() => { 478 | class TestServer extends Server { 479 | _serverCreate() { 480 | } 481 | } 482 | 483 | const server = new TestServer({ 484 | "host": "host.com", 485 | "port": 99, 486 | "ipv6Only": false, 487 | "requestCert": true, 488 | "cert": "mycert", 489 | "key": "mykey", 490 | "ca": "myca", 491 | }); 492 | 493 | let flag = false; 494 | assert(flag == false); 495 | const fn = function() { 496 | flag = true; 497 | }; 498 | //@ts-ignore: private method 499 | server.on("testevent", fn); 500 | //@ts-ignore: protected method 501 | server.triggerEvent("testevent", fn); 502 | //@ts-ignore: data is changed inside trigger event callback 503 | assert(flag == true); 504 | }); 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /test/TCPClient.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {TCPClient} from "../index"; 3 | 4 | const assert = require("assert"); 5 | const net = require("net"); 6 | const tls = require("tls"); 7 | 8 | @TestSuite() 9 | export class TCPClientSocketConnect { 10 | 11 | @Test() 12 | public successful_call_setting_USE_TLS() { 13 | assert.doesNotThrow(() => { 14 | const client = new TCPClient({ 15 | "host": "host.com", 16 | "port": 99, 17 | "secure": true, 18 | }); 19 | //@ts-ignore: protected data 20 | assert(client.clientOptions!.host == "host.com"); 21 | //@ts-ignore: protected data 22 | assert(client.clientOptions!.port == 99); 23 | //@ts-ignore: protected data 24 | assert(client.clientOptions!.secure == true); 25 | //@ts-ignore: protected data 26 | assert(client.clientOptions!.rejectUnauthorized == null); 27 | 28 | //@ts-ignore: protected data 29 | assert(client.socket == null); 30 | //@ts-ignore: protected data 31 | assert(client._isClosed == false); 32 | 33 | //@ts-ignore: overwrite read-only 34 | tls.connect = function() { 35 | return { 36 | "on": function(name: string, fn: Function) { 37 | assert(name == "secureConnect"); 38 | assert(typeof fn == "function"); 39 | } 40 | }; 41 | }; 42 | //@ts-ignore 43 | client.socketConnect(); 44 | }); 45 | } 46 | 47 | @Test() 48 | public successful_call_without_USE_TLS() { 49 | assert.doesNotThrow(() => { 50 | const client = new TCPClient({ 51 | "host": "host.com", 52 | "port": 99, 53 | "secure": false, 54 | }); 55 | //@ts-ignore: protected data 56 | assert(client.clientOptions!.host == "host.com"); 57 | //@ts-ignore: protected data 58 | assert(client.clientOptions!.port == 99); 59 | //@ts-ignore: protected data 60 | assert(client.clientOptions!.secure == false); 61 | //@ts-ignore: protected data 62 | assert(client.clientOptions!.rejectUnauthorized == null); 63 | 64 | //@ts-ignore: protected data 65 | assert(client.socket == null); 66 | //@ts-ignore: protected data 67 | assert(client._isClosed == false); 68 | 69 | //@ts-ignore: overwrite read-only 70 | net.connect = function() { 71 | return { 72 | "on": function(name: string, fn: Function) { 73 | assert(name == "connect"); 74 | assert(typeof fn == "function"); 75 | } 76 | }; 77 | }; 78 | //@ts-ignore 79 | client.socketConnect(); 80 | }); 81 | } 82 | 83 | @Test() 84 | public successful_call_with_existing_socket() { 85 | assert.doesNotThrow(() => { 86 | //@ts-ignore: incomplete implementation 87 | let socket = new net.Socket(); 88 | let flagOnEvent = false; 89 | //@ts-ignore: incomplete inheritance 90 | class TestClient extends TCPClient { 91 | triggerEvent(evt: string, data: any) { 92 | if(evt == "close") { 93 | flagOnEvent = true; 94 | } 95 | } 96 | } 97 | //@ts-ignore: incomplete socket specification 98 | const client = new TCPClient({ 99 | "host": "host.com", 100 | "port": 99, 101 | "secure": false, 102 | }, /*@ts-ignore*/ socket); 103 | 104 | //@ts-ignore: protected data 105 | assert(client.socket != null); 106 | 107 | //@ts-ignore: protected method 108 | client.triggerEvent("close"); 109 | }); 110 | } 111 | 112 | @Test() 113 | public socket_already_created() { 114 | assert.throws(() => { 115 | //@ts-ignore: incomplete implementation 116 | let socket = new net.Socket(); 117 | //@ts-ignore: incomplete socket specification 118 | const client = new TCPClient({ 119 | "host": "host.com", 120 | "port": 99, 121 | "secure": false, 122 | }, /*@ts-ignore*/ socket); 123 | 124 | //@ts-ignore: protected data 125 | assert(client.socket != null); 126 | client.connect(); 127 | }, /Socket already created/); 128 | } 129 | 130 | @Test() 131 | public missing_client_options() { 132 | assert.throws(() => { 133 | //@ts-ignore: incomplete socket specification 134 | const client = new TCPClient({ 135 | "host": "host.com", 136 | "port": 99, 137 | "secure": false, 138 | }); 139 | 140 | //@ts-ignore: protected data 141 | client.clientOptions = undefined; 142 | client.connect(); 143 | }, /clientOptions is required to create socket/); 144 | } 145 | 146 | 147 | } 148 | 149 | @TestSuite() 150 | export class TCPClientSocketHook { 151 | @Test() 152 | public successful_call() { 153 | assert.doesNotThrow(() => { 154 | const client = new TCPClient({ 155 | "host": "host.com", 156 | "port": 99, 157 | "secure": false, 158 | }); 159 | let counter = 0; 160 | //@ts-ignore 161 | client.socket = { 162 | "on": function(name: string, fn: Function): any { 163 | counter++; 164 | assert(name == "data" || name == "error" || name == "close" ); 165 | assert(typeof fn == "function"); 166 | return null; 167 | } 168 | }; 169 | assert(counter == 0); 170 | //@ts-ignore 171 | client.socketHook(); 172 | assert(counter == 3); 173 | }); 174 | } 175 | } 176 | 177 | @TestSuite() 178 | export class TCPClientSocketSend { 179 | @Test() 180 | public successful_call() { 181 | assert.doesNotThrow(() => { 182 | const client = new TCPClient({ 183 | "host": "host.com", 184 | "port": 99, 185 | "secure": false, 186 | }); 187 | //@ts-ignore: incomplete socket implementation 188 | client.socket = { 189 | "write": function(buffer: Buffer): boolean { 190 | assert(buffer.toString() == "testdata123"); 191 | assert(Buffer.isBuffer(buffer)); 192 | return true; 193 | } 194 | }; 195 | //@ts-ignore: protected method 196 | client.socketSend(Buffer.from("testdata123")); 197 | }); 198 | } 199 | } 200 | 201 | @TestSuite() 202 | export class TCPClientSocketClose { 203 | @Test() 204 | public successful_call() { 205 | assert.doesNotThrow(() => { 206 | const client = new TCPClient({ 207 | "host": "host.com", 208 | "port": 99, 209 | "secure": false, 210 | }); 211 | let hasEnded = false; 212 | //@ts-ignore: incomplete socket implementation 213 | client.socket = { 214 | "end": function() { 215 | hasEnded = true; 216 | } 217 | }; 218 | assert(hasEnded == false); 219 | //@ts-ignore: protected method 220 | client.socketClose(); 221 | //@ts-ignore 222 | assert(hasEnded == true); 223 | }); 224 | } 225 | } 226 | 227 | @TestSuite() 228 | export class TCPClientSocketError { 229 | @Test() 230 | public successful_call() { 231 | assert.doesNotThrow(() => { 232 | const client = new TCPClient({ 233 | "host": "host.com", 234 | "port": 99, 235 | "secure": false, 236 | }); 237 | let flag = false; 238 | //@ts-ignore: protected method 239 | client.socketError = function(data: Buffer) { 240 | flag = true; 241 | //@ts-ignore 242 | assert(data.toString() == "Test Error"); 243 | }; 244 | assert(flag == false); 245 | //@ts-ignore: protected method 246 | client.error(new Error("Test Error")); 247 | //@ts-ignore 248 | assert(flag == true); 249 | }); 250 | } 251 | } 252 | 253 | @TestSuite() 254 | export class TCPClientGetLocalAddress { 255 | @Test() 256 | public retrieve_undefined() { 257 | assert.doesNotThrow(() => { 258 | const client = new TCPClient({ 259 | "host": "host.com", 260 | "port": 99, 261 | "secure": false, 262 | }); 263 | //@ts-ignore: incomplete implementation 264 | client.socket = { 265 | } 266 | assert(client.getLocalAddress() == undefined); 267 | }); 268 | } 269 | 270 | @Test() 271 | public retrieve_undefined_when_null() { 272 | assert.doesNotThrow(() => { 273 | const client = new TCPClient({ 274 | "host": "host.com", 275 | "port": 99, 276 | "secure": false, 277 | }); 278 | //@ts-ignore: incomplete implementation 279 | client.socket = { 280 | //@ts-ignore: unexpected data conversion 281 | localAddress: null 282 | } 283 | assert(client.getLocalAddress() == undefined); 284 | }); 285 | } 286 | 287 | @Test() 288 | public retrieve_string() { 289 | assert.doesNotThrow(() => { 290 | const client = new TCPClient({ 291 | "host": "host.com", 292 | "port": 99, 293 | "secure": false, 294 | }); 295 | //@ts-ignore: incomplete implementation 296 | client.socket = { 297 | localAddress: "host.com" 298 | } 299 | assert(client.getLocalAddress() == "host.com"); 300 | }); 301 | } 302 | } 303 | 304 | @TestSuite() 305 | export class TCPClientGetRemoteAddress { 306 | @Test() 307 | public retrieve_undefined() { 308 | assert.doesNotThrow(() => { 309 | const client = new TCPClient({ 310 | "host": "host.com", 311 | "port": 99, 312 | "secure": false, 313 | }); 314 | //@ts-ignore: incomplete implementation 315 | client.socket = { 316 | } 317 | assert(client.getRemoteAddress() == undefined); 318 | }); 319 | } 320 | 321 | @Test() 322 | public retrieve_undefined_when_null() { 323 | assert.doesNotThrow(() => { 324 | const client = new TCPClient({ 325 | "host": "host.com", 326 | "port": 99, 327 | "secure": false, 328 | }); 329 | //@ts-ignore: incomplete implementation 330 | client.socket = { 331 | //@ts-ignore: unexpected data conversion 332 | remoteAddress: null 333 | } 334 | assert(client.getRemoteAddress() == undefined); 335 | }); 336 | } 337 | 338 | @Test() 339 | public retrieve_string() { 340 | assert.doesNotThrow(() => { 341 | const client = new TCPClient({ 342 | "host": "host.com", 343 | "port": 99, 344 | "secure": false, 345 | }); 346 | //@ts-ignore: incomplete implementation 347 | client.socket = { 348 | remoteAddress: "host.com" 349 | } 350 | assert(client.getRemoteAddress() == "host.com"); 351 | }); 352 | } 353 | } 354 | 355 | @TestSuite() 356 | export class TCPClientGetRemotePort { 357 | @Test() 358 | public retrieve_undefined() { 359 | assert.doesNotThrow(() => { 360 | const client = new TCPClient({ 361 | "host": "host.com", 362 | "port": 99, 363 | "secure": false, 364 | }); 365 | //@ts-ignore: incomplete implementation 366 | client.socket = { 367 | } 368 | assert(client.getRemotePort() == undefined); 369 | }); 370 | } 371 | 372 | @Test() 373 | public retrieve_undefined_when_null() { 374 | assert.doesNotThrow(() => { 375 | const client = new TCPClient({ 376 | "host": "host.com", 377 | "port": 99, 378 | "secure": false, 379 | }); 380 | //@ts-ignore: incomplete implementation 381 | client.socket = { 382 | //@ts-ignore: unexpected data conversion 383 | remotePort: null 384 | } 385 | assert(client.getRemotePort() == undefined); 386 | }); 387 | } 388 | 389 | @Test() 390 | public retrieve_string() { 391 | assert.doesNotThrow(() => { 392 | const client = new TCPClient({ 393 | "host": "host.com", 394 | "port": 99, 395 | "secure": false, 396 | }); 397 | //@ts-ignore: incomplete implementation 398 | client.socket = { 399 | remotePort: 999 400 | } 401 | assert(client.getRemotePort() == 999); 402 | }); 403 | } 404 | } 405 | 406 | @TestSuite() 407 | export class TCPClientGetLocalPort { 408 | @Test() 409 | public retrieve_undefined() { 410 | assert.doesNotThrow(() => { 411 | const client = new TCPClient({ 412 | "host": "host.com", 413 | "port": 99, 414 | "secure": false, 415 | }); 416 | //@ts-ignore: incomplete implementation 417 | client.socket = { 418 | } 419 | assert(client.getLocalPort() == undefined); 420 | }); 421 | } 422 | 423 | @Test() 424 | public retrieve_undefined_when_null() { 425 | assert.doesNotThrow(() => { 426 | const client = new TCPClient({ 427 | "host": "host.com", 428 | "port": 99, 429 | "secure": false, 430 | }); 431 | //@ts-ignore: incomplete implementation 432 | client.socket = { 433 | //@ts-ignore: unexpected data conversion 434 | localPort: null 435 | } 436 | assert(client.getLocalPort() == undefined); 437 | }); 438 | } 439 | 440 | @Test() 441 | public retrieve_string() { 442 | assert.doesNotThrow(() => { 443 | const client = new TCPClient({ 444 | "host": "host.com", 445 | "port": 99, 446 | "secure": false, 447 | }); 448 | //@ts-ignore: incomplete implementation 449 | client.socket = { 450 | localPort: 999 451 | } 452 | assert(client.getLocalPort() == 999); 453 | }); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /test/TCPServer.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {TCPClient, TCPServer} from "../index"; 3 | 4 | const assert = require("assert"); 5 | const net = require("net"); 6 | const tls = require("tls"); 7 | 8 | @TestSuite() 9 | export class TCPServerConstructor { 10 | @Test() 11 | public successful_call_with_cert() { 12 | assert.doesNotThrow(() => { 13 | tls.createServer = function() { 14 | return { 15 | "on": function(name: string, fn: Function) { 16 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 17 | assert(typeof fn == "function"); 18 | } 19 | } 20 | } 21 | const server = new TCPServer({ 22 | "host": "host.com", 23 | "port": 99, 24 | "rejectUnauthorized": undefined, 25 | "cert": "valid-certificate" 26 | }); 27 | //@ts-ignore: protected data 28 | assert(server.server); 29 | }); 30 | } 31 | 32 | @Test() 33 | public successful_call_without_TLS() { 34 | assert.doesNotThrow(() => { 35 | net.createServer = function() { 36 | return { 37 | "on": function(name: string, fn: Function) { 38 | assert(name == "connection" || name == "error" || name == "close" || name == "tlsClientError"); 39 | assert(typeof fn == "function"); 40 | } 41 | }; 42 | }; 43 | const server = new TCPServer({ 44 | "host": "host.com", 45 | "port": 99, 46 | }); 47 | //@ts-ignore: protected data 48 | assert(server.server); 49 | }); 50 | } 51 | 52 | @Test() 53 | public calls_serverCreate() { 54 | assert.doesNotThrow(() => { 55 | let flag = false; 56 | class TestServer extends TCPServer { 57 | serverCreate() { 58 | flag = true; 59 | } 60 | } 61 | const server = new TestServer({ 62 | "host": "host.com", 63 | "port": 99, 64 | }); 65 | //@ts-ignore: state is expected to be changed by custom serverCreate 66 | assert(flag == true); 67 | }); 68 | } 69 | 70 | } 71 | 72 | @TestSuite() 73 | export class TCPServerCreate { 74 | @Test() 75 | public successful_call_with_USE_TLS() { 76 | assert.doesNotThrow(() => { 77 | tls.createServer = function() { 78 | return { 79 | "on": function(name: string, fn: Function) { 80 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 81 | assert(typeof fn == "function"); 82 | } 83 | } 84 | }; 85 | const server = new TCPServer({ 86 | "host": "host.com", 87 | "port": 99, 88 | "rejectUnauthorized": undefined, 89 | "cert": "valid-certificate" 90 | }); 91 | //@ts-ignore: protected data 92 | assert(server.server); 93 | }); 94 | } 95 | 96 | @Test() 97 | public successful_call_without_USE_TLS() { 98 | assert.doesNotThrow(() => { 99 | net.createServer = function() { 100 | return { 101 | "on": function(name: string, fn: Function) { 102 | assert(name == "connection" || name == "error" || name == "close" || name == "tlsClientError"); 103 | assert(typeof fn == "function"); 104 | } 105 | }; 106 | }; 107 | const server = new TCPServer({ 108 | "host": "host.com", 109 | "port": 99, 110 | "rejectUnauthorized": undefined 111 | }); 112 | //@ts-ignore: protected data 113 | assert(server.server); 114 | }); 115 | } 116 | } 117 | 118 | @TestSuite() 119 | export class TCPServerListen { 120 | @Test() 121 | public overwritten_server_after_object_creation() { 122 | assert.doesNotThrow(() => { 123 | tls.createServer = function() { 124 | return { 125 | "on": function(name: string, fn: Function) { 126 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 127 | assert(typeof fn == "function"); 128 | } 129 | }; 130 | }; 131 | const server = new TCPServer({ 132 | "host": "host.com", 133 | "port": 99, 134 | "rejectUnauthorized": undefined, 135 | "cert": "valid-certificate" 136 | }); 137 | //@ts-ignore: protected data 138 | server.server = undefined; 139 | //@ts-ignore: protected method 140 | server.serverListen(); 141 | }); 142 | } 143 | 144 | @Test() 145 | public successful_call() { 146 | assert.doesNotThrow(() => { 147 | tls.createServer = function() { 148 | return { 149 | "on": function(name: string, fn: Function) { 150 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 151 | assert(typeof fn == "function"); 152 | }, 153 | "listen": function(options: any) { 154 | assert(options.host == "host.com"); 155 | assert(options.port == 99); 156 | assert(!options.ipv6Only); 157 | } 158 | 159 | }; 160 | }; 161 | const server = new TCPServer({ 162 | "host": "host.com", 163 | "port": 99, 164 | "rejectUnauthorized": undefined, 165 | "cert": "valid-certificate" 166 | }); 167 | //@ts-ignore: protected method 168 | server.serverListen(); 169 | }); 170 | } 171 | } 172 | 173 | @TestSuite() 174 | export class TCPServerClose { 175 | @Test() 176 | public successful_call() { 177 | assert.doesNotThrow(() => { 178 | const server = new TCPServer({ 179 | "host": "host.com", 180 | "port": 99, 181 | "rejectUnauthorized": undefined, 182 | "cert": "valid-certificate" 183 | }); 184 | let flag = false; 185 | //@ts-ignore: custom signature 186 | server.server!.close = function() { 187 | flag = true; 188 | } 189 | assert(flag == false); 190 | //@ts-ignore: protected method 191 | server.serverClose(); 192 | //@ts-ignore: expected to be mutated by custom close procedure 193 | assert(flag == true); 194 | }); 195 | } 196 | } 197 | 198 | @TestSuite() 199 | export class TCPServerClientConnected { 200 | @Test() 201 | public successful_call() { 202 | assert.doesNotThrow(() => { 203 | const server = new TCPServer({ 204 | "host": "host.com", 205 | "port": 99, 206 | "rejectUnauthorized": undefined, 207 | "cert": "valid-certificate" 208 | }); 209 | let flag = false; 210 | //@ts-ignore: custom signature 211 | server.addClient = function(client) { 212 | assert(client); 213 | flag = true; 214 | } 215 | assert(flag == false); 216 | //@ts-ignore: protected method 217 | server.clientConnected(); 218 | //@ts-ignore: expected to be mutated by custom procedure 219 | assert(flag == true); 220 | }); 221 | } 222 | } 223 | 224 | @TestSuite() 225 | export class TCPServerError { 226 | @Test() 227 | public successful_call() { 228 | assert.doesNotThrow(() => { 229 | const server = new TCPServer({ 230 | "host": "host.com", 231 | "port": 99, 232 | "rejectUnauthorized": undefined, 233 | "cert": "valid-certificate" 234 | }); 235 | let flag = false; 236 | //@ts-ignore: custom signature 237 | server.serverError = function(buffer) { 238 | assert(buffer.toString() == "Error Message Here"); 239 | flag = true; 240 | } 241 | assert(flag == false); 242 | //@ts-ignore: protected method 243 | server.error(new Error("Error Message Here")); 244 | //@ts-ignore: expected to be mutated by custom procedure 245 | assert(flag == true); 246 | }); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /test/VirtualClient.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {CreatePair} from "../index"; 3 | //@ts-ignore 4 | import {VirtualClient} from "../src/VirtualClient"; 5 | 6 | const assert = require("assert"); 7 | 8 | @TestSuite() 9 | export class VirtualClientConstructor { 10 | /*@Test() 11 | public missing_pairedSocket() { 12 | let client; 13 | assert.doesNotThrow(() => { 14 | client = new VirtualClient(); 15 | //@ts-ignore 16 | assert(!client.pairedSocket); 17 | }); 18 | }*/ 19 | 20 | @Test() 21 | public successful_call() { 22 | let client; 23 | assert.doesNotThrow(() => { 24 | let client1, client2; 25 | [client1, client2 ] = CreatePair(); 26 | //@ts-ignore 27 | assert(client1.pairedSocket); 28 | //@ts-ignore 29 | assert(client2.pairedSocket); 30 | //@ts-ignore 31 | assert(client2.pairedSocket!.pairedSocket); 32 | //@ts-ignore 33 | assert(client2.pairedSocket!.outQueue.length == 0); 34 | }); 35 | } 36 | } 37 | 38 | @TestSuite() 39 | export class VirtualClientSetLatency { 40 | @Test() 41 | public successful_call() { 42 | let client; 43 | assert.doesNotThrow(() => { 44 | let client1, client2; 45 | [client1, client2 ] = CreatePair(); 46 | //@ts-ignore 47 | assert(client1.latency == 0); 48 | client1.setLatency(20); 49 | //@ts-ignore 50 | assert(client1.latency == 20); 51 | }); 52 | } 53 | } 54 | 55 | @TestSuite() 56 | export class VirtualClientSocketHook { 57 | @Test() 58 | public noop_call() { 59 | let client; 60 | assert.doesNotThrow(() => { 61 | let client1, client2; 62 | [client1, client2 ] = CreatePair(); 63 | //@ts-ignore: protected method 64 | client1.socketHook(); 65 | //@ts-ignore: protected method 66 | client2.socketHook(); 67 | }); 68 | } 69 | } 70 | 71 | @TestSuite() 72 | export class VirtualClientSocketSend { 73 | /*@Test() 74 | public call_without_paired_socket() { 75 | let client; 76 | assert.doesNotThrow(() => { 77 | client = new VirtualClient(); 78 | assert(client.outQueue.length == 0); 79 | //@ts-ignore 80 | client.socketSend(Buffer.from("testdata")); 81 | assert(client.outQueue.length == 0); 82 | }); 83 | }*/ 84 | 85 | @Test() 86 | public successful_call() { 87 | let client; 88 | assert.doesNotThrow(() => { 89 | let client1, client2; 90 | [client1, client2 ] = CreatePair(); 91 | //@ts-ignore 92 | assert(client1.outQueue.length == 0); 93 | 94 | let called = false; 95 | //@ts-ignore 96 | client1.copyToPaired = function() { 97 | called = true; 98 | } 99 | 100 | //@ts-ignore 101 | client1.socketSend(Buffer.from("testdata")); 102 | //@ts-ignore: value is expected to be changed by call to copyToPaired 103 | assert(called == true); 104 | }); 105 | } 106 | } 107 | 108 | @TestSuite() 109 | export class VirtualClientSocketClosed { 110 | @Test() 111 | public successful_call() { 112 | let client; 113 | assert.doesNotThrow(() => { 114 | let client1: any, client2: any; 115 | [client1, client2 ] = CreatePair(); 116 | 117 | let closeCounter = 0; 118 | 119 | //@ts-ignore: protected method 120 | client1.socketClosed = function() { 121 | closeCounter++; 122 | } 123 | //@ts-ignore: protected method 124 | client2.socketClosed = function() { 125 | closeCounter++; 126 | } 127 | 128 | assert(closeCounter == 0); 129 | //@ts-ignore: protected method 130 | client1.socketClose(); 131 | assert(closeCounter == 2); 132 | }); 133 | } 134 | } 135 | 136 | @TestSuite() 137 | export class VirtualClientCopyToPaired { 138 | @Test() 139 | public successful_call() { 140 | let client; 141 | assert.doesNotThrow(() => { 142 | let client1, client2; 143 | [client1, client2 ] = CreatePair(); 144 | 145 | let copiedBuffer: Buffer; 146 | //@ts-ignore 147 | client2.socketData = function(buffer) { 148 | if(Buffer.isBuffer(buffer)) { 149 | copiedBuffer = buffer; 150 | } 151 | } 152 | 153 | //@ts-ignore 154 | client1.socketSend(Buffer.from("fromclient1")); 155 | //@ts-ignore 156 | client1.copyToPaired(); 157 | assert(copiedBuffer!.toString() == "fromclient1"); 158 | }); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /test/WSClient.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {WSClient} from "../index"; 3 | 4 | const assert = require("assert"); 5 | const net = require("net"); 6 | const tls = require("tls"); 7 | const ws = require("ws"); 8 | 9 | @TestSuite() 10 | export class WSClientSocketConstructor { 11 | @Test() 12 | public successful_call_setting_USE_TLS() { 13 | assert.doesNotThrow(() => { 14 | const client = new WSClient({ 15 | "host": "host.com", 16 | "port": 99, 17 | "secure": true, 18 | }); 19 | //@ts-ignore: protected data 20 | assert(client.clientOptions!.host == "host.com"); 21 | //@ts-ignore: protected data 22 | assert(client.clientOptions!.port == 99); 23 | //@ts-ignore: protected data 24 | assert(client.clientOptions!.secure == true); 25 | 26 | //@ts-ignore: protected data 27 | assert(client.socket == null); 28 | }); 29 | } 30 | 31 | @Test() 32 | public successful_call_without_TLS() { 33 | assert.doesNotThrow(() => { 34 | assert.doesNotThrow(() => { 35 | const client = new WSClient({ 36 | "host": "host.com", 37 | "port": 99, 38 | "secure": false, 39 | }); 40 | //@ts-ignore: protected data 41 | assert(client.clientOptions!.host == "host.com"); 42 | //@ts-ignore: protected data 43 | assert(client.clientOptions!.port == 99); 44 | //@ts-ignore: protected data 45 | assert(client.clientOptions!.secure == false); 46 | 47 | //@ts-ignore: protected data 48 | assert(client.socket == null); 49 | }); 50 | }); 51 | } 52 | 53 | @Test() 54 | public successful_call_with_existing_socket() { 55 | assert.doesNotThrow(() => { 56 | let flag = false; 57 | class TestClient extends WSClient { 58 | socketHook() { 59 | flag = true; 60 | } 61 | } 62 | assert(flag == false); 63 | //@ts-ignore incomplete socket definition 64 | let socket: ws.WebSocket = {}; 65 | //@ts-ignore incomplete socket definition 66 | const client = new TestClient({ 67 | "host": "host.com", 68 | "port": 99, 69 | "secure": false, 70 | }, /*@ts-ignore*/ socket); 71 | assert(flag == true); 72 | }); 73 | } 74 | } 75 | 76 | @TestSuite() 77 | export class WSClientSocketHook { 78 | @Test() 79 | public successful_call() { 80 | assert.doesNotThrow(() => { 81 | const client = new WSClient({ 82 | "host": "host.com", 83 | "port": 99, 84 | "secure": false, 85 | }); 86 | //@ts-ignore: dummy 87 | client.socketConnect = function() { 88 | //@ts-ignore: incomplete implementation 89 | client.socket = {}; 90 | }; 91 | 92 | //@ts-ignore: validate flow 93 | client.socketConnect(); 94 | //@ts-ignore: protected data 95 | assert(!client.socket!.onmessage); 96 | //@ts-ignore: protected data 97 | assert(!client.socket!.onerror); 98 | //@ts-ignore: protected data 99 | assert(!client.socket!.onclose); 100 | //@ts-ignore: protected method 101 | client.socketHook(); 102 | //@ts-ignore: protected data 103 | assert(client.socket!.onmessage); 104 | //@ts-ignore: protected data 105 | assert(client.socket!.onerror); 106 | //@ts-ignore: protected data 107 | assert(client.socket!.onclose); 108 | }); 109 | } 110 | 111 | @Test() 112 | public missing_socket_noop() { 113 | assert.doesNotThrow(() => { 114 | const client = new WSClient({ 115 | "host": "host.com", 116 | "port": 99, 117 | "secure": false, 118 | }); 119 | //@ts-ignore: protected data 120 | client.socket = undefined; 121 | //@ts-ignore: protected method 122 | client.socketHook(); 123 | }); 124 | } 125 | } 126 | 127 | @TestSuite() 128 | export class WSClientSocketConnect { 129 | @Test() 130 | public socket_already_created() { 131 | assert.throws(() => { 132 | //@ts-ignore incomplete socket definition 133 | let socket: ws.WebSocket = {}; 134 | //@ts-ignore incomplete socket definition 135 | const client = new WSClient({ 136 | "host": "host.com", 137 | "port": 99, 138 | "secure": false, 139 | }, /*@ts-ignore*/ socket); 140 | assert(client.socketConnect()); 141 | }, /Socket already created/); 142 | } 143 | 144 | @Test() 145 | public undefined_options() { 146 | assert.throws(() => { 147 | const client = new WSClient({ 148 | "host": "host.com", 149 | "port": 99, 150 | "secure": false, 151 | }); 152 | //@ts-ignore: protected data 153 | client.clientOptions = undefined; 154 | //@ts-ignore: protected method 155 | assert(client.socketConnect()); 156 | }, /clientOptions is required to create socket/); 157 | } 158 | } 159 | 160 | @TestSuite() 161 | export class WSClientSocketSend { 162 | @Test() 163 | public successful_call_setting_USE_TLS() { 164 | assert.doesNotThrow(() => { 165 | const client = new WSClient({ 166 | "host": "host.com", 167 | "port": 99, 168 | "secure": false, 169 | }); 170 | //@ts-ignore: incomplete implementation 171 | client.socket = { 172 | "send": function(buffer: Buffer, options: any) { 173 | assert(buffer.toString() == "testdata123"); 174 | assert(Buffer.isBuffer(buffer)); 175 | assert(options.binary == true); 176 | assert(options.compress == false); 177 | } 178 | }; 179 | //@ts-ignore: protected method 180 | client.socketSend(Buffer.from("testdata123")); 181 | }); 182 | } 183 | 184 | @Test() 185 | public missing_socket_noop() { 186 | assert.doesNotThrow(() => { 187 | const client = new WSClient({ 188 | "host": "host.com", 189 | "port": 99, 190 | "secure": false, 191 | }); 192 | //@ts-ignore: protected data 193 | client.socket = undefined; 194 | //@ts-ignore: protected method 195 | assert.throws( () => client.socketSend(Buffer.from("testdata123")), 196 | /Socket not instantiated/ ); 197 | }); 198 | } 199 | 200 | } 201 | 202 | @TestSuite() 203 | export class WSClientSocketClose { 204 | @Test() 205 | public successful_call() { 206 | assert.doesNotThrow(() => { 207 | const client = new WSClient({ 208 | "host": "host.com", 209 | "port": 99, 210 | "secure": false, 211 | }); 212 | //@ts-ignore: protected method 213 | let hasClosed = false; 214 | //@ts-ignore: incomplete implementation 215 | client.socket = { 216 | "close": function() { 217 | hasClosed = true; 218 | } 219 | }; 220 | assert(hasClosed == false); 221 | //@ts-ignore: protected method 222 | client.socketClose(); 223 | //@ts-ignore: data expected to be changed by previously called inner functions 224 | assert(hasClosed == true); 225 | }); 226 | } 227 | 228 | @Test() 229 | public missing_socket_noop() { 230 | assert.doesNotThrow(() => { 231 | const client = new WSClient({ 232 | "host": "host.com", 233 | "port": 99, 234 | "secure": false, 235 | }); 236 | //@ts-ignore: protected data 237 | client.socket = undefined; 238 | //@ts-ignore: protected method 239 | client.socketClose(); 240 | }); 241 | } 242 | } 243 | 244 | @TestSuite() 245 | export class WSClientError { 246 | @Test() 247 | public successful_call() { 248 | assert.doesNotThrow(() => { 249 | const client = new WSClient({ 250 | "host": "host.com", 251 | "port": 99, 252 | "secure": false, 253 | }); 254 | let flag = false; 255 | //@ts-ignore: protected method 256 | client.socketError = function(msg: string) { 257 | assert(msg == "test error"); 258 | flag = true; 259 | }; 260 | assert(flag == false); 261 | //@ts-ignore: protected method 262 | client.error(new Error("test error")); 263 | //@ts-ignore: flag changes inside custom callback 264 | assert(flag == true); 265 | }); 266 | } 267 | 268 | @Test() 269 | public onerror_missing_error_message_succeeds() { 270 | assert.doesNotThrow(() => { 271 | const client = new WSClient({ 272 | "host": "host.com", 273 | "port": 99, 274 | "secure": false, 275 | }); 276 | let flag = false; 277 | //@ts-ignore: protected method 278 | client.socketError = function(msg: string) { 279 | assert(msg == "WebSocket could not connect"); 280 | flag = true; 281 | }; 282 | //@ts-ignore: ignore missing input check 283 | client.error(new Error()); 284 | //@ts-ignore: flag changes inside custom callback 285 | assert(flag == true); 286 | }); 287 | } 288 | 289 | public onerror_undefined_error_succeeds() { 290 | assert.doesNotThrow(() => { 291 | const client = new WSClient({ 292 | "host": "host.com", 293 | "port": 99, 294 | "secure": false, 295 | }); 296 | let flag = false; 297 | //@ts-ignore: protected method 298 | client.socketError = function(msg: string) { 299 | flag = true; 300 | }; 301 | //@ts-ignore: ignore missing input check 302 | client.error(); 303 | //@ts-ignore: flag changes inside custom callback 304 | assert(flag == true); 305 | }); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /test/WSServer.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {WSServer, WSClient} from "../index"; 3 | 4 | const assert = require("assert"); 5 | const http = require("http"); 6 | const https = require("https"); 7 | const WebSocket = require("ws"); 8 | 9 | @TestSuite() 10 | export class WSServerConstructor { 11 | @Test() 12 | public calls_serverCreate() { 13 | assert.doesNotThrow(() => { 14 | https.createServer = function() { 15 | return { 16 | "on": function(name: string, fn: Function) { 17 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 18 | assert(typeof fn == "function"); 19 | } 20 | }; 21 | }; 22 | let flag = false; 23 | class TestServer extends WSServer { 24 | serverCreate() { 25 | flag = true; 26 | } 27 | } 28 | assert(flag == false); 29 | const server = new TestServer({ 30 | "host": "host.com", 31 | "port": 99, 32 | "cert": "valid-certificate" 33 | }); 34 | //@ts-ignore: modified by custom serverCreate 35 | assert(flag == true); 36 | }); 37 | } 38 | 39 | 40 | @Test() 41 | public successful_call_with_cert() { 42 | assert.doesNotThrow(() => { 43 | https.createServer = function() { 44 | return { 45 | "on": function(name: string, fn: Function) { 46 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 47 | assert(typeof fn == "function"); 48 | } 49 | }; 50 | }; 51 | const server = new WSServer({ 52 | "host": "host.com", 53 | "port": 99, 54 | "cert": "valid-certificate" 55 | }); 56 | //@ts-ignore: protected data 57 | assert(server.wsServer == null); 58 | }); 59 | } 60 | 61 | @Test() 62 | public successful_call_without_TLS() { 63 | assert.doesNotThrow(() => { 64 | http.createServer = function() { 65 | return { 66 | "on": function(name: string, fn: Function) { 67 | assert(name == "connection" || name == "error" || name == "close" || name == "tlsClientError"); 68 | assert(typeof fn == "function"); 69 | } 70 | }; 71 | }; 72 | const server = new WSServer({ 73 | "host": "host.com", 74 | "port": 99, 75 | }); 76 | //@ts-ignore: protected data 77 | assert(server.wsServer == null); 78 | }); 79 | } 80 | } 81 | 82 | @TestSuite() 83 | export class WSServerCreate { 84 | @Test() 85 | public successful_call_with_USE_TLS() { 86 | https.createServer = function() { 87 | return { 88 | "on": function(name: string, fn: Function) { 89 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 90 | assert(typeof fn == "function"); 91 | } 92 | }; 93 | }; 94 | const server = new WSServer({ 95 | "host": "host.com", 96 | "port": 99, 97 | "cert": "valid-certificate" 98 | }); 99 | //@ts-ignore: protected data 100 | assert(server.server); 101 | } 102 | 103 | @Test() 104 | public successful_call_without_USE_TLS() { 105 | assert.doesNotThrow(() => { 106 | https.createServer = function() { 107 | return { 108 | "on": function(name: string, fn: Function) { 109 | assert(name == "connection" || name == "error" || name == "close" || name == "tlsClientError"); 110 | assert(typeof fn == "function"); 111 | } 112 | }; 113 | }; 114 | const server = new WSServer({ 115 | "host": "host.com", 116 | "port": 99, 117 | }); 118 | //@ts-ignore: protected data 119 | assert(server.server); 120 | }); 121 | } 122 | 123 | @Test() 124 | public undefined_return_from_createServer() { 125 | assert.doesNotThrow(() => { 126 | http.createServer = function() { 127 | return undefined; 128 | }; 129 | const server = new WSServer({ 130 | "host": "host.com", 131 | "port": 99, 132 | }); 133 | //@ts-ignore: protected data 134 | assert(!server.server); 135 | }); 136 | } 137 | } 138 | 139 | @TestSuite() 140 | export class WSServerListen { 141 | @Test() 142 | public overwritten_server_after_object_creation() { 143 | assert.throws(() => { 144 | https.createServer = function() { 145 | return { 146 | "on": function(name: string, fn: Function) { 147 | assert(name == "secureConnection" || name == "error" || name == "close" || name == "tlsClientError"); 148 | assert(typeof fn == "function"); 149 | } 150 | }; 151 | } 152 | const server = new WSServer({ 153 | "host": "host.com", 154 | "port": 99, 155 | "cert": "valid-certificate" 156 | }); 157 | //@ts-ignore: protected data 158 | server.server = undefined; 159 | //@ts-ignore: protected method 160 | server.serverListen(); 161 | }); 162 | } 163 | 164 | @Test() 165 | public successful_call() { 166 | assert.doesNotThrow(() => { 167 | https.createServer = function() { 168 | return { 169 | "on": function(name: string, fn: Function) { 170 | assert(name == "connection" || name == "error" || name == "close" || name == "tlsClientError" || name == "listening" || name == "upgrade"); 171 | assert(typeof fn == "function"); 172 | }, 173 | "listen": function(options: any) { 174 | assert(options.host == "host.com"); 175 | assert(options.port == 99); 176 | assert(!options.ipv6Only); 177 | } 178 | }; 179 | }; 180 | 181 | WebSocket.Server = function(data: any) { 182 | assert(data!.path == "/"); 183 | assert(data!.clientTracking == true); 184 | assert(data!.perMessageDeflate == false); 185 | assert(data!.maxPayload == 100*1024*1024); 186 | return { 187 | "on": function(name: string, fn: Function) { 188 | assert(name == "connection" || name == "error" || name == "close"); 189 | assert(typeof fn == "function"); 190 | } 191 | }; 192 | }; 193 | 194 | const server = new WSServer({ 195 | "host": "host.com", 196 | "port": 99, 197 | "cert": "valid-certificate" 198 | }); 199 | //@ts-ignore: protected data 200 | assert(!server.wsServer); 201 | //@ts-ignore: protected method 202 | server.serverListen(); 203 | //@ts-ignore: protected data 204 | assert(server.wsServer); 205 | }); 206 | } 207 | } 208 | 209 | @TestSuite() 210 | export class WSServerClose { 211 | @Test() 212 | public successful_call() { 213 | assert.doesNotThrow(() => { 214 | https.createServer = function() { 215 | return { 216 | "on": function(name: string, fn: Function) { 217 | }, 218 | "listen": function(options: any) { 219 | } 220 | }; 221 | }; 222 | WebSocket.Server = function() { 223 | return { 224 | "on": function(name: string, fn: Function) { 225 | } 226 | }; 227 | }; 228 | const server = new WSServer({ 229 | "host": "host.com", 230 | "port": 99, 231 | "cert": "valid-certificate" 232 | }); 233 | //@ts-ignore: protected method 234 | server.serverListen(); 235 | 236 | let callCount = 0; 237 | //@ts-ignore: different signature is not relevant 238 | server.server!.close = function() { 239 | callCount++; 240 | } 241 | //@ts-ignore: different signature is not relevant 242 | server.wsServer!.close = function() { 243 | callCount++; 244 | } 245 | assert(callCount == 0); 246 | //@ts-ignore: protected method 247 | server.serverClose(); 248 | assert(callCount == 2); 249 | }); 250 | } 251 | } 252 | 253 | @TestSuite() 254 | export class WSServerClientConnected { 255 | @Test() 256 | public pass_socket_to_clientConnected() { 257 | assert.doesNotThrow(() => { 258 | https.createServer = function() { 259 | return { 260 | "on": function(name: string, fn: Function) { 261 | assert(name == "connection" || name == "error" || name == "close" || name == "tlsClientError"); 262 | assert(typeof fn == "function"); 263 | } 264 | }; 265 | }; 266 | const server = new WSServer({ 267 | "host": "host.com", 268 | "port": 99, 269 | }); 270 | //@ts-ignore: protected data 271 | assert(server.clients.length == 0); 272 | //@ts-ignore: protected method 273 | server.clientConnected(server); 274 | //@ts-ignore: protected data 275 | assert(server.clients.length == 1); 276 | }); 277 | } 278 | } 279 | @TestSuite() 280 | export class WSServerError { 281 | @Test() 282 | public successful_call() { 283 | assert.doesNotThrow(() => { 284 | https.createServer = function() { 285 | return { 286 | "on": function(name: string, fn: Function) { 287 | assert(name == "connection" || name == "error" || name == "close" || name == "tlsClientError"); 288 | assert(typeof fn == "function"); 289 | } 290 | }; 291 | }; 292 | const server = new WSServer({ 293 | "host": "host.com", 294 | "port": 99, 295 | }); 296 | let flag = false; 297 | //@ts-ignore: protected method 298 | server.serverError = function(buffer: Buffer) { 299 | assert(buffer.toString() == "test error"); 300 | flag = true; 301 | }; 302 | assert(flag == false); 303 | //@ts-ignore: protected method 304 | server.error(new Error("test error")); 305 | //@ts-ignore: flag changes inside custom callback 306 | assert(flag == true); 307 | }); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /test/cert/generate_self_signed_cert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | SUBJECT=/C=SE/ST=Unknown/L=Stockholm/O=PocketSocketsTestCA 4 | 5 | # Root Certificate Authority 6 | CA="testCA" 7 | openssl genrsa -out "${CA}".key 2048 8 | openssl req -x509 -new -nodes -key "${CA}".key -sha256 -days 36500 -out "${CA}".cert -subj "${SUBJECT}"/CN="${CA}" 9 | openssl x509 -in "${CA}".cert -out "${CA}".pem -text 10 | 11 | # Server 12 | NAME="localhost" 13 | openssl genrsa -out "${NAME}".key 2048 14 | openssl req -new -out "${NAME}".csr -key "${NAME}".key -subj "${SUBJECT}"/CN="${NAME}" 15 | openssl x509 -req -days 36500 -in "${NAME}".csr -out "${NAME}".cert -CA "${CA}".cert -CAkey "${CA}".key -CAcreateserial 16 | 17 | # Client 18 | NAME="testClient" 19 | openssl genrsa -out "${NAME}".key 2048 20 | openssl req -new -key "${NAME}".key -out "${NAME}".csr -subj "${SUBJECT}"/CN="${NAME}" 21 | openssl x509 -req -days 36500 -in "${NAME}".csr -CA "${CA}".pem -CAkey "${CA}".key -CAcreateserial -out "${NAME}".cert 22 | -------------------------------------------------------------------------------- /test/connection.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {WSClient} from "../index"; 3 | import {WSServer, ClientInterface} from "../index"; 4 | 5 | const assert = require("assert"); 6 | 7 | @TestSuite() 8 | export class Connection { 9 | 10 | @Test() 11 | public async clientserver() { 12 | await new Promise(resolve => { 13 | const serverOptions = { 14 | host: "localhost", 15 | port: 8181 16 | }; 17 | const server = new WSServer(serverOptions); 18 | server.listen(); 19 | 20 | server.onConnection( (client: ClientInterface) => { 21 | client.onData( (data: Buffer | string) => { 22 | assert(data.toString() == "hello"); 23 | client.send("received!"); 24 | }); 25 | client.onClose( () => { 26 | server.close(); 27 | resolve(); 28 | }); 29 | }); 30 | 31 | const clientOptions = { 32 | host: "localhost", 33 | port: 8181 34 | }; 35 | const client = new WSClient(clientOptions); 36 | client.connect(); 37 | 38 | client.onConnect( () => { 39 | client.onData( (data: Buffer | string) => { 40 | assert(data.toString() == "received!"); 41 | client.close(); 42 | }); 43 | client.onClose( () => { 44 | }); 45 | client.send("hello"); 46 | }); 47 | }); 48 | } 49 | 50 | @Test() 51 | public async clientserver_ipv4() { 52 | await new Promise(resolve => { 53 | const serverOptions = { 54 | host: "127.0.0.1", 55 | port: 8181 56 | }; 57 | const server = new WSServer(serverOptions); 58 | server.listen(); 59 | 60 | server.onConnection( (client: ClientInterface) => { 61 | client.onData( (data: Buffer | string) => { 62 | assert(data.toString() == "hello"); 63 | client.send("received!"); 64 | }); 65 | client.onClose( () => { 66 | server.close(); 67 | resolve(); 68 | }); 69 | }); 70 | 71 | const clientOptions = { 72 | host: "127.0.0.1", 73 | port: 8181 74 | }; 75 | const client = new WSClient(clientOptions); 76 | client.connect(); 77 | 78 | client.onConnect( () => { 79 | client.onData( (data: Buffer | string) => { 80 | assert(data.toString() == "received!"); 81 | client.close(); 82 | }); 83 | client.onClose( () => { 84 | }); 85 | client.send("hello"); 86 | }); 87 | }); 88 | } 89 | 90 | @Test() 91 | public async clientserver_ipv6_short() { 92 | await new Promise(resolve => { 93 | const serverOptions = { 94 | host: "::1", 95 | port: 8181 96 | }; 97 | const server = new WSServer(serverOptions); 98 | server.listen(); 99 | 100 | server.onConnection( (client: ClientInterface) => { 101 | client.onData( (data: Buffer | string) => { 102 | assert(data.toString() == "hello"); 103 | client.send("received!"); 104 | }); 105 | client.onClose( () => { 106 | server.close(); 107 | resolve(); 108 | }); 109 | }); 110 | 111 | const clientOptions = { 112 | host: "::1", 113 | port: 8181 114 | }; 115 | const client = new WSClient(clientOptions); 116 | client.connect(); 117 | 118 | client.onConnect( () => { 119 | client.onData( (data: Buffer | string) => { 120 | assert(data.toString() == "received!"); 121 | client.close(); 122 | }); 123 | client.onClose( () => { 124 | }); 125 | client.send("hello"); 126 | }); 127 | }); 128 | } 129 | 130 | @Test() 131 | public async clientserver_ipv6_long() { 132 | await new Promise(resolve => { 133 | const serverOptions = { 134 | host: "0:0:0:0:0:0:0:1", 135 | port: 8181 136 | }; 137 | const server = new WSServer(serverOptions); 138 | server.listen(); 139 | 140 | server.onConnection( (client: ClientInterface) => { 141 | client.onData( (data: Buffer | string) => { 142 | assert(data.toString() == "hello"); 143 | client.send("received!"); 144 | }); 145 | client.onClose( () => { 146 | server.close(); 147 | resolve(); 148 | }); 149 | }); 150 | 151 | const clientOptions = { 152 | host: "0:0:0:0:0:0:0:1", 153 | port: 8181 154 | }; 155 | const client = new WSClient(clientOptions); 156 | client.connect(); 157 | 158 | client.onConnect( () => { 159 | client.onData( (data: Buffer | string) => { 160 | assert(data.toString() == "received!"); 161 | client.close(); 162 | }); 163 | client.onClose( () => { 164 | }); 165 | client.send("hello"); 166 | }); 167 | }); 168 | } 169 | 170 | @Test() 171 | public async clientserver_missing_host() { 172 | await new Promise(resolve => { 173 | const serverOptions = { 174 | port: 8182 175 | }; 176 | const server = new WSServer(serverOptions); 177 | server.listen(); 178 | 179 | server.onConnection( (client: ClientInterface) => { 180 | client.onData( (data: Buffer | string) => { 181 | assert(data.toString() == "hello"); 182 | server.close(); 183 | resolve(); 184 | }); 185 | }); 186 | 187 | const clientOptions = { 188 | port: 8182 189 | }; 190 | const client = new WSClient(clientOptions); 191 | client.connect(); 192 | 193 | client.onConnect( () => { 194 | client.onData( (data: Buffer | string) => { 195 | client.close(); 196 | }); 197 | client.send("hello"); 198 | }); 199 | }); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /test/connectionTCP.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {TCPClient} from "../index"; 3 | import {TCPServer, ClientInterface} from "../index"; 4 | 5 | const assert = require("assert"); 6 | 7 | @TestSuite() 8 | export class ConnectionTCP { 9 | 10 | @Test() 11 | public async clientserver() { 12 | await new Promise(resolve => { 13 | const serverOptions = { 14 | host: "localhost", 15 | port: 8181 16 | }; 17 | const server = new TCPServer(serverOptions); 18 | server.listen(); 19 | 20 | server.onConnection( (client: ClientInterface) => { 21 | client.onData( (data: Buffer | string) => { 22 | assert(data.toString() == "hello"); 23 | client.send("received!"); 24 | }); 25 | client.onClose( () => { 26 | server.close(); 27 | resolve(); 28 | }); 29 | }); 30 | 31 | const clientOptions = { 32 | host: "localhost", 33 | port: 8181 34 | }; 35 | const client = new TCPClient(clientOptions); 36 | client.connect(); 37 | 38 | client.onConnect( () => { 39 | client.onData( (data: Buffer | string) => { 40 | assert(data.toString() == "received!"); 41 | client.close(); 42 | }); 43 | client.onClose( () => { 44 | }); 45 | client.send("hello"); 46 | }); 47 | }); 48 | } 49 | 50 | @Test() 51 | public async clientserver_ipv4() { 52 | await new Promise(resolve => { 53 | const serverOptions = { 54 | host: "127.0.0.1", 55 | port: 8181 56 | }; 57 | const server = new TCPServer(serverOptions); 58 | server.listen(); 59 | 60 | server.onConnection( (client: ClientInterface) => { 61 | client.onData( (data: Buffer | string) => { 62 | assert(data.toString() == "hello"); 63 | client.send("received!"); 64 | }); 65 | client.onClose( () => { 66 | server.close(); 67 | resolve(); 68 | }); 69 | }); 70 | 71 | const clientOptions = { 72 | host: "127.0.0.1", 73 | port: 8181 74 | }; 75 | const client = new TCPClient(clientOptions); 76 | client.connect(); 77 | 78 | client.onConnect( () => { 79 | client.onData( (data: Buffer | string) => { 80 | assert(data.toString() == "received!"); 81 | client.close(); 82 | }); 83 | client.onClose( () => { 84 | }); 85 | client.send("hello"); 86 | }); 87 | }); 88 | } 89 | 90 | @Test() 91 | public async clientserver_ipv6_short() { 92 | await new Promise(resolve => { 93 | const serverOptions = { 94 | host: "::1", 95 | port: 8181 96 | }; 97 | const server = new TCPServer(serverOptions); 98 | server.listen(); 99 | 100 | server.onConnection( (client: ClientInterface) => { 101 | client.onData( (data: Buffer | string) => { 102 | assert(data.toString() == "hello"); 103 | client.send("received!"); 104 | }); 105 | client.onClose( () => { 106 | server.close(); 107 | resolve(); 108 | }); 109 | }); 110 | 111 | const clientOptions = { 112 | host: "::1", 113 | port: 8181 114 | }; 115 | const client = new TCPClient(clientOptions); 116 | client.connect(); 117 | 118 | client.onConnect( () => { 119 | client.onData( (data: Buffer | string) => { 120 | assert(data.toString() == "received!"); 121 | client.close(); 122 | }); 123 | client.onClose( () => { 124 | }); 125 | client.send("hello"); 126 | }); 127 | }); 128 | } 129 | 130 | @Test() 131 | public async clientserver_ipv6_long() { 132 | await new Promise(resolve => { 133 | const serverOptions = { 134 | host: "0:0:0:0:0:0:0:1", 135 | port: 8181 136 | }; 137 | const server = new TCPServer(serverOptions); 138 | server.listen(); 139 | 140 | server.onConnection( (client: ClientInterface) => { 141 | client.onData( (data: Buffer | string) => { 142 | assert(data.toString() == "hello"); 143 | client.send("received!"); 144 | }); 145 | client.onClose( () => { 146 | server.close(); 147 | resolve(); 148 | }); 149 | }); 150 | 151 | const clientOptions = { 152 | host: "0:0:0:0:0:0:0:1", 153 | port: 8181 154 | }; 155 | const client = new TCPClient(clientOptions); 156 | client.connect(); 157 | 158 | client.onConnect( () => { 159 | client.onData( (data: Buffer | string) => { 160 | assert(data.toString() == "received!"); 161 | client.close(); 162 | }); 163 | client.onClose( () => { 164 | }); 165 | client.send("hello"); 166 | }); 167 | }); 168 | } 169 | 170 | @Test() 171 | public async clientserver_missing_host() { 172 | await new Promise(resolve => { 173 | const serverOptions = { 174 | port: 8182 175 | }; 176 | const server = new TCPServer(serverOptions); 177 | server.listen(); 178 | 179 | server.onConnection( (client: ClientInterface) => { 180 | client.onData( (data: Buffer | string) => { 181 | assert(data.toString() == "hello"); 182 | server.close(); 183 | resolve(); 184 | }); 185 | }); 186 | 187 | const clientOptions = { 188 | port: 8182 189 | }; 190 | const client = new TCPClient(clientOptions); 191 | client.connect(); 192 | 193 | client.onConnect( () => { 194 | client.onData( (data: Buffer | string) => { 195 | client.close(); 196 | }); 197 | client.send("hello"); 198 | }); 199 | }); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /test/connectionTCPTLS.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {TCPClient} from "../index"; 3 | import {TCPServer, ClientInterface} from "../index"; 4 | 5 | const assert = require("assert"); 6 | const fs = require("fs"); 7 | 8 | @TestSuite() 9 | export class ConnectionTCPTLS { 10 | 11 | @Test() 12 | public async clientserver_server_reject_unauthorized() { 13 | await new Promise(resolve => { 14 | const serverOptions = { 15 | host: "localhost", 16 | port: 8181, 17 | requestCert: true, 18 | rejectUnauthorized: true, 19 | cert: fs.readFileSync("./test/cert/localhost.cert"), 20 | key: fs.readFileSync("./test/cert/localhost.key"), 21 | ca: fs.readFileSync("./test/cert/testCA.cert") 22 | }; 23 | const server = new TCPServer(serverOptions); 24 | server.listen(); 25 | 26 | server.onConnection( (client: ClientInterface) => { 27 | client.onData( (data: Buffer | string) => { 28 | assert(data.toString() == "hello"); 29 | client.send("received!"); 30 | }); 31 | client.onClose( () => { 32 | server.close(); 33 | resolve(); 34 | }); 35 | }); 36 | 37 | const clientOptions = { 38 | host: "localhost", 39 | port: 8181, 40 | secure: true, 41 | rejectUnauthorized: false, 42 | cert: fs.readFileSync("./test/cert/testClient.cert"), 43 | key: fs.readFileSync("./test/cert/testClient.key"), 44 | ca: fs.readFileSync("./test/cert/testCA.cert") 45 | }; 46 | const client = new TCPClient(clientOptions); 47 | client.connect(); 48 | 49 | client.onConnect( () => { 50 | client.onData( (data: Buffer | string) => { 51 | assert(data.toString() == "received!"); 52 | client.close(); 53 | }); 54 | client.onClose( () => { 55 | }); 56 | client.send("hello"); 57 | }); 58 | }); 59 | } 60 | 61 | @Test() 62 | public async clientserver_client_reject_unauthorized() { 63 | await new Promise(resolve => { 64 | const serverOptions = { 65 | host: "localhost", 66 | port: 8181, 67 | requestCert: true, 68 | rejectUnauthorized: false, 69 | cert: fs.readFileSync("./test/cert/localhost.cert"), 70 | key: fs.readFileSync("./test/cert/localhost.key"), 71 | ca: fs.readFileSync("./test/cert/testCA.cert") 72 | }; 73 | const server = new TCPServer(serverOptions); 74 | server.listen(); 75 | 76 | server.onConnection( (client: ClientInterface) => { 77 | client.onData( (data: Buffer | string) => { 78 | assert(data.toString() == "hello"); 79 | client.send("received!"); 80 | }); 81 | client.onClose( () => { 82 | server.close(); 83 | resolve(); 84 | }); 85 | }); 86 | 87 | const clientOptions = { 88 | host: "localhost", 89 | port: 8181, 90 | secure: true, 91 | rejectUnauthorized: true, 92 | cert: fs.readFileSync("./test/cert/testClient.cert"), 93 | key: fs.readFileSync("./test/cert/testClient.key"), 94 | ca: fs.readFileSync("./test/cert/testCA.cert") 95 | }; 96 | const client = new TCPClient(clientOptions); 97 | client.connect(); 98 | 99 | client.onConnect( () => { 100 | client.onData( (data: Buffer | string) => { 101 | assert(data.toString() == "received!"); 102 | client.close(); 103 | }); 104 | client.onClose( () => { 105 | }); 106 | client.send("hello"); 107 | }); 108 | }); 109 | } 110 | 111 | @Test() 112 | public async clientserver_reject_unauthorized() { 113 | await new Promise(resolve => { 114 | const serverOptions = { 115 | host: "localhost", 116 | port: 8181, 117 | requestCert: true, 118 | rejectUnauthorized: true, 119 | cert: fs.readFileSync("./test/cert/localhost.cert"), 120 | key: fs.readFileSync("./test/cert/localhost.key"), 121 | ca: fs.readFileSync("./test/cert/testCA.cert") 122 | }; 123 | const server = new TCPServer(serverOptions); 124 | server.listen(); 125 | 126 | server.onConnection( (client: ClientInterface) => { 127 | client.onData( (data: Buffer | string) => { 128 | assert(data.toString() == "hello"); 129 | client.send("received!"); 130 | }); 131 | client.onClose( () => { 132 | server.close(); 133 | resolve(); 134 | }); 135 | }); 136 | 137 | const clientOptions = { 138 | host: "localhost", 139 | port: 8181, 140 | secure: true, 141 | rejectUnauthorized: true, 142 | cert: fs.readFileSync("./test/cert/testClient.cert"), 143 | key: fs.readFileSync("./test/cert/testClient.key"), 144 | ca: fs.readFileSync("./test/cert/testCA.cert") 145 | }; 146 | const client = new TCPClient(clientOptions); 147 | client.connect(); 148 | 149 | client.onConnect( () => { 150 | client.onData( (data: Buffer | string) => { 151 | assert(data.toString() == "received!"); 152 | client.close(); 153 | }); 154 | client.onClose( () => { 155 | }); 156 | client.send("hello"); 157 | }); 158 | }); 159 | } 160 | 161 | @Test() 162 | public async clientserver() { 163 | await new Promise(resolve => { 164 | const serverOptions = { 165 | host: "localhost", 166 | port: 8181, 167 | requestCert: true, 168 | rejectUnauthorized: false, 169 | cert: fs.readFileSync("./test/cert/localhost.cert"), 170 | key: fs.readFileSync("./test/cert/localhost.key") 171 | }; 172 | const server = new TCPServer(serverOptions); 173 | server.listen(); 174 | 175 | server.onConnection( (client: ClientInterface) => { 176 | client.onData( (data: Buffer | string) => { 177 | assert(data.toString() == "hello"); 178 | client.send("received!"); 179 | }); 180 | client.onClose( () => { 181 | server.close(); 182 | resolve(); 183 | }); 184 | }); 185 | 186 | const clientOptions = { 187 | host: "localhost", 188 | port: 8181, 189 | secure: true, 190 | rejectUnauthorized: false, 191 | cert: fs.readFileSync("./test/cert/testClient.cert"), 192 | key: fs.readFileSync("./test/cert/testClient.key") 193 | }; 194 | const client = new TCPClient(clientOptions); 195 | client.connect(); 196 | 197 | client.onConnect( () => { 198 | client.onData( (data: Buffer | string) => { 199 | assert(data.toString() == "received!"); 200 | client.close(); 201 | }); 202 | client.onClose( () => { 203 | }); 204 | client.send("hello"); 205 | }); 206 | }); 207 | } 208 | 209 | @Test() 210 | public async clientserver_ipv4() { 211 | await new Promise(resolve => { 212 | const serverOptions = { 213 | host: "127.0.0.1", 214 | port: 8181, 215 | requestCert: true, 216 | rejectUnauthorized: false, 217 | cert: fs.readFileSync("./test/cert/localhost.cert"), 218 | key: fs.readFileSync("./test/cert/localhost.key") 219 | }; 220 | const server = new TCPServer(serverOptions); 221 | server.listen(); 222 | 223 | server.onConnection( (client: ClientInterface) => { 224 | client.onData( (data: Buffer | string) => { 225 | assert(data.toString() == "hello"); 226 | client.send("received!"); 227 | }); 228 | client.onClose( () => { 229 | server.close(); 230 | resolve(); 231 | }); 232 | }); 233 | 234 | const clientOptions = { 235 | host: "127.0.0.1", 236 | port: 8181, 237 | secure: true, 238 | rejectUnauthorized: false, 239 | cert: fs.readFileSync("./test/cert/testClient.cert"), 240 | key: fs.readFileSync("./test/cert/testClient.key") 241 | }; 242 | const client = new TCPClient(clientOptions); 243 | client.connect(); 244 | 245 | client.onConnect( () => { 246 | client.onData( (data: Buffer | string) => { 247 | assert(data.toString() == "received!"); 248 | client.close(); 249 | }); 250 | client.onClose( () => { 251 | }); 252 | client.send("hello"); 253 | }); 254 | }); 255 | } 256 | 257 | @Test() 258 | public async clientserver_ipv6_short() { 259 | await new Promise(resolve => { 260 | const serverOptions = { 261 | host: "::1", 262 | port: 8181, 263 | requestCert: true, 264 | rejectUnauthorized: false, 265 | cert: fs.readFileSync("./test/cert/localhost.cert"), 266 | key: fs.readFileSync("./test/cert/localhost.key") 267 | }; 268 | const server = new TCPServer(serverOptions); 269 | server.listen(); 270 | 271 | server.onConnection( (client: ClientInterface) => { 272 | client.onData( (data: Buffer | string) => { 273 | assert(data.toString() == "hello"); 274 | client.send("received!"); 275 | }); 276 | client.onClose( () => { 277 | server.close(); 278 | resolve(); 279 | }); 280 | }); 281 | 282 | const clientOptions = { 283 | host: "::1", 284 | port: 8181, 285 | secure: true, 286 | rejectUnauthorized: false, 287 | cert: fs.readFileSync("./test/cert/testClient.cert"), 288 | key: fs.readFileSync("./test/cert/testClient.key") 289 | }; 290 | const client = new TCPClient(clientOptions); 291 | client.connect(); 292 | 293 | client.onConnect( () => { 294 | client.onData( (data: Buffer | string) => { 295 | assert(data.toString() == "received!"); 296 | client.close(); 297 | }); 298 | client.onClose( () => { 299 | }); 300 | client.send("hello"); 301 | }); 302 | }); 303 | } 304 | 305 | @Test() 306 | public async clientserver_ipv6_long() { 307 | await new Promise(resolve => { 308 | const serverOptions = { 309 | host: "0:0:0:0:0:0:0:1", 310 | port: 8181, 311 | requestCert: true, 312 | rejectUnauthorized: false, 313 | cert: fs.readFileSync("./test/cert/localhost.cert"), 314 | key: fs.readFileSync("./test/cert/localhost.key") 315 | }; 316 | const server = new TCPServer(serverOptions); 317 | server.listen(); 318 | 319 | server.onConnection( (client: ClientInterface) => { 320 | client.onData( (data: Buffer | string) => { 321 | assert(data.toString() == "hello"); 322 | client.send("received!"); 323 | }); 324 | client.onClose( () => { 325 | server.close(); 326 | resolve(); 327 | }); 328 | }); 329 | 330 | const clientOptions = { 331 | host: "0:0:0:0:0:0:0:1", 332 | port: 8181, 333 | secure: true, 334 | rejectUnauthorized: false, 335 | cert: fs.readFileSync("./test/cert/testClient.cert"), 336 | key: fs.readFileSync("./test/cert/testClient.key") 337 | }; 338 | const client = new TCPClient(clientOptions); 339 | client.connect(); 340 | 341 | client.onConnect( () => { 342 | client.onData( (data: Buffer | string) => { 343 | assert(data.toString() == "received!"); 344 | client.close(); 345 | }); 346 | client.onClose( () => { 347 | }); 348 | client.send("hello"); 349 | }); 350 | }); 351 | } 352 | 353 | @Test() 354 | public async clientserver_missing_host() { 355 | await new Promise(resolve => { 356 | const serverOptions = { 357 | port: 8182, 358 | requestCert: true, 359 | rejectUnauthorized: false, 360 | cert: fs.readFileSync("./test/cert/localhost.cert"), 361 | key: fs.readFileSync("./test/cert/localhost.key") 362 | }; 363 | const server = new TCPServer(serverOptions); 364 | server.listen(); 365 | 366 | server.onConnection( (client: ClientInterface) => { 367 | client.onData( (data: Buffer | string) => { 368 | assert(data.toString() == "hello"); 369 | server.close(); 370 | resolve(); 371 | }); 372 | }); 373 | 374 | const clientOptions = { 375 | port: 8182, 376 | secure: true, 377 | rejectUnauthorized: false, 378 | cert: fs.readFileSync("./test/cert/testClient.cert"), 379 | key: fs.readFileSync("./test/cert/testClient.key") 380 | }; 381 | const client = new TCPClient(clientOptions); 382 | client.connect(); 383 | 384 | client.onConnect( () => { 385 | client.onData( (data: Buffer | string) => { 386 | client.close(); 387 | }); 388 | client.send("hello"); 389 | }); 390 | }); 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /test/connectionTLS.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestSuite, Test } from "testyts"; 2 | import {WSClient} from "../index"; 3 | import {WSServer, ClientInterface} from "../index"; 4 | 5 | const assert = require("assert"); 6 | const fs = require("fs"); 7 | 8 | @TestSuite() 9 | export class ConnectionTLS { 10 | 11 | @Test() 12 | public async clientserver_server_reject_unauthorized() { 13 | await new Promise(resolve => { 14 | const serverOptions = { 15 | host: "localhost", 16 | port: 8181, 17 | requestCert: true, 18 | rejectUnauthorized: true, 19 | cert: fs.readFileSync("./test/cert/localhost.cert"), 20 | key: fs.readFileSync("./test/cert/localhost.key"), 21 | ca: fs.readFileSync("./test/cert/testCA.pem") 22 | }; 23 | const server = new WSServer(serverOptions); 24 | server.listen(); 25 | 26 | server.onConnection( (client: ClientInterface) => { 27 | client.onData( (data: Buffer | string) => { 28 | assert(data.toString() == "hello"); 29 | client.send("received!"); 30 | }); 31 | client.onClose( () => { 32 | server.close(); 33 | resolve(); 34 | }); 35 | }); 36 | 37 | const clientOptions = { 38 | host: "localhost", 39 | port: 8181, 40 | secure: true, 41 | rejectUnauthorized: false, 42 | cert: fs.readFileSync("./test/cert/testClient.cert"), 43 | key: fs.readFileSync("./test/cert/testClient.key"), 44 | ca: fs.readFileSync("./test/cert/testCA.pem") 45 | }; 46 | const client = new WSClient(clientOptions); 47 | client.connect(); 48 | 49 | client.onConnect( () => { 50 | client.onData( (data: Buffer | string) => { 51 | assert(data.toString() == "received!"); 52 | client.close(); 53 | }); 54 | client.onClose( () => { 55 | }); 56 | client.send("hello"); 57 | }); 58 | }); 59 | } 60 | 61 | @Test() 62 | public async clientserver_client_reject_unauthorized() { 63 | await new Promise(resolve => { 64 | const serverOptions = { 65 | host: "localhost", 66 | port: 8181, 67 | requestCert: true, 68 | rejectUnauthorized: false, 69 | cert: fs.readFileSync("./test/cert/localhost.cert"), 70 | key: fs.readFileSync("./test/cert/localhost.key"), 71 | ca: fs.readFileSync("./test/cert/testCA.pem") 72 | }; 73 | const server = new WSServer(serverOptions); 74 | server.listen(); 75 | 76 | server.onConnection( (client: ClientInterface) => { 77 | client.onData( (data: Buffer | string) => { 78 | assert(data.toString() == "hello"); 79 | client.send("received!"); 80 | }); 81 | client.onClose( () => { 82 | server.close(); 83 | resolve(); 84 | }); 85 | }); 86 | 87 | const clientOptions = { 88 | host: "localhost", 89 | port: 8181, 90 | secure: true, 91 | rejectUnauthorized: true, 92 | cert: fs.readFileSync("./test/cert/testClient.cert"), 93 | key: fs.readFileSync("./test/cert/testClient.key"), 94 | ca: fs.readFileSync("./test/cert/testCA.pem") 95 | }; 96 | const client = new WSClient(clientOptions); 97 | client.connect(); 98 | 99 | client.onConnect( () => { 100 | client.onData( (data: Buffer | string) => { 101 | assert(data.toString() == "received!"); 102 | client.close(); 103 | }); 104 | client.onClose( () => { 105 | }); 106 | client.send("hello"); 107 | }); 108 | }); 109 | } 110 | 111 | @Test() 112 | public async clientserver_reject_unauthorized() { 113 | await new Promise(resolve => { 114 | const serverOptions = { 115 | host: "localhost", 116 | port: 8181, 117 | requestCert: true, 118 | rejectUnauthorized: true, 119 | cert: fs.readFileSync("./test/cert/localhost.cert"), 120 | key: fs.readFileSync("./test/cert/localhost.key"), 121 | ca: fs.readFileSync("./test/cert/testCA.pem") 122 | }; 123 | const server = new WSServer(serverOptions); 124 | server.listen(); 125 | 126 | server.onConnection( (client: ClientInterface) => { 127 | client.onData( (data: Buffer | string) => { 128 | assert(data.toString() == "hello"); 129 | client.send("received!"); 130 | }); 131 | client.onClose( () => { 132 | server.close(); 133 | resolve(); 134 | }); 135 | }); 136 | 137 | const clientOptions = { 138 | host: "localhost", 139 | port: 8181, 140 | secure: true, 141 | rejectUnauthorized: true, 142 | cert: fs.readFileSync("./test/cert/testClient.cert"), 143 | key: fs.readFileSync("./test/cert/testClient.key"), 144 | ca: fs.readFileSync("./test/cert/testCA.pem") 145 | }; 146 | const client = new WSClient(clientOptions); 147 | client.connect(); 148 | 149 | client.onConnect( () => { 150 | client.onData( (data: Buffer | string) => { 151 | assert(data.toString() == "received!"); 152 | client.close(); 153 | }); 154 | client.onClose( () => { 155 | }); 156 | client.send("hello"); 157 | }); 158 | }); 159 | } 160 | 161 | @Test() 162 | public async clientserver_allow_unauthorized() { 163 | await new Promise(resolve => { 164 | const serverOptions = { 165 | host: "localhost", 166 | port: 8181, 167 | requestCert: true, 168 | rejectUnauthorized: false, 169 | cert: fs.readFileSync("./test/cert/localhost.cert"), 170 | key: fs.readFileSync("./test/cert/localhost.key") 171 | }; 172 | const server = new WSServer(serverOptions); 173 | server.listen(); 174 | 175 | server.onConnection( (client: ClientInterface) => { 176 | client.onData( (data: Buffer | string) => { 177 | assert(data.toString() == "hello"); 178 | client.send("received!"); 179 | }); 180 | client.onClose( () => { 181 | server.close(); 182 | resolve(); 183 | }); 184 | }); 185 | 186 | const clientOptions = { 187 | host: "localhost", 188 | port: 8181, 189 | secure: true, 190 | rejectUnauthorized: false, 191 | cert: fs.readFileSync("./test/cert/testClient.cert"), 192 | key: fs.readFileSync("./test/cert/testClient.key") 193 | }; 194 | const client = new WSClient(clientOptions); 195 | client.connect(); 196 | 197 | client.onConnect( () => { 198 | client.onData( (data: Buffer | string) => { 199 | assert(data.toString() == "received!"); 200 | client.close(); 201 | }); 202 | client.onClose( () => { 203 | }); 204 | client.send("hello"); 205 | }); 206 | }); 207 | } 208 | 209 | @Test() 210 | public async clientserver_ipv4() { 211 | await new Promise(resolve => { 212 | const serverOptions = { 213 | host: "127.0.0.1", 214 | port: 8181, 215 | requestCert: true, 216 | rejectUnauthorized: false, 217 | cert: fs.readFileSync("./test/cert/localhost.cert"), 218 | key: fs.readFileSync("./test/cert/localhost.key") 219 | }; 220 | const server = new WSServer(serverOptions); 221 | server.listen(); 222 | 223 | server.onConnection( (client: ClientInterface) => { 224 | client.onData( (data: Buffer | string) => { 225 | assert(data.toString() == "hello"); 226 | client.send("received!"); 227 | }); 228 | client.onClose( () => { 229 | server.close(); 230 | resolve(); 231 | }); 232 | }); 233 | 234 | const clientOptions = { 235 | host: "127.0.0.1", 236 | port: 8181, 237 | secure: true, 238 | rejectUnauthorized: false, 239 | cert: fs.readFileSync("./test/cert/testClient.cert"), 240 | key: fs.readFileSync("./test/cert/testClient.key") 241 | }; 242 | const client = new WSClient(clientOptions); 243 | client.connect(); 244 | 245 | client.onConnect( () => { 246 | client.onData( (data: Buffer | string) => { 247 | assert(data.toString() == "received!"); 248 | client.close(); 249 | }); 250 | client.onClose( () => { 251 | }); 252 | client.send("hello"); 253 | }); 254 | }); 255 | } 256 | 257 | @Test() 258 | public async clientserver_ipv6_short() { 259 | await new Promise(resolve => { 260 | const serverOptions = { 261 | host: "::1", 262 | port: 8181, 263 | requestCert: true, 264 | rejectUnauthorized: false, 265 | cert: fs.readFileSync("./test/cert/localhost.cert"), 266 | key: fs.readFileSync("./test/cert/localhost.key") 267 | }; 268 | const server = new WSServer(serverOptions); 269 | server.listen(); 270 | 271 | server.onConnection( (client: ClientInterface) => { 272 | client.onData( (data: Buffer | string) => { 273 | assert(data.toString() == "hello"); 274 | client.send("received!"); 275 | }); 276 | client.onClose( () => { 277 | server.close(); 278 | resolve(); 279 | }); 280 | }); 281 | 282 | const clientOptions = { 283 | host: "::1", 284 | port: 8181, 285 | secure: true, 286 | rejectUnauthorized: false, 287 | cert: fs.readFileSync("./test/cert/testClient.cert"), 288 | key: fs.readFileSync("./test/cert/testClient.key") 289 | }; 290 | const client = new WSClient(clientOptions); 291 | client.connect(); 292 | 293 | client.onConnect( () => { 294 | client.onData( (data: Buffer | string) => { 295 | assert(data.toString() == "received!"); 296 | client.close(); 297 | }); 298 | client.onClose( () => { 299 | }); 300 | client.send("hello"); 301 | }); 302 | }); 303 | } 304 | 305 | @Test() 306 | public async clientserver_ipv6_long() { 307 | await new Promise(resolve => { 308 | const serverOptions = { 309 | host: "0:0:0:0:0:0:0:1", 310 | port: 8181, 311 | requestCert: true, 312 | rejectUnauthorized: false, 313 | cert: fs.readFileSync("./test/cert/localhost.cert"), 314 | key: fs.readFileSync("./test/cert/localhost.key") 315 | }; 316 | const server = new WSServer(serverOptions); 317 | server.listen(); 318 | 319 | server.onConnection( (client: ClientInterface) => { 320 | client.onData( (data: Buffer | string) => { 321 | assert(data.toString() == "hello"); 322 | client.send("received!"); 323 | }); 324 | client.onClose( () => { 325 | server.close(); 326 | resolve(); 327 | }); 328 | }); 329 | 330 | const clientOptions = { 331 | host: "0:0:0:0:0:0:0:1", 332 | port: 8181, 333 | secure: true, 334 | rejectUnauthorized: false, 335 | cert: fs.readFileSync("./test/cert/testClient.cert"), 336 | key: fs.readFileSync("./test/cert/testClient.key") 337 | }; 338 | const client = new WSClient(clientOptions); 339 | client.connect(); 340 | 341 | client.onConnect( () => { 342 | client.onData( (data: Buffer | string) => { 343 | assert(data.toString() == "received!"); 344 | client.close(); 345 | }); 346 | client.onClose( () => { 347 | }); 348 | client.send("hello"); 349 | }); 350 | }); 351 | } 352 | 353 | @Test() 354 | public async clientserver_missing_host() { 355 | await new Promise(resolve => { 356 | const serverOptions = { 357 | port: 8182, 358 | requestCert: true, 359 | rejectUnauthorized: false, 360 | cert: fs.readFileSync("./test/cert/localhost.cert"), 361 | key: fs.readFileSync("./test/cert/localhost.key") 362 | }; 363 | const server = new WSServer(serverOptions); 364 | server.listen(); 365 | 366 | server.onConnection( (client: ClientInterface) => { 367 | client.onData( (data: Buffer | string) => { 368 | assert(data.toString() == "hello"); 369 | server.close(); 370 | resolve(); 371 | }); 372 | }); 373 | 374 | const clientOptions = { 375 | port: 8182, 376 | secure: true, 377 | rejectUnauthorized: false, 378 | cert: fs.readFileSync("./test/cert/testClient.cert"), 379 | key: fs.readFileSync("./test/cert/testClient.key") 380 | }; 381 | const client = new WSClient(clientOptions); 382 | client.connect(); 383 | 384 | client.onConnect( () => { 385 | client.onData( (data: Buffer | string) => { 386 | client.close(); 387 | }); 388 | client.send("hello"); 389 | }); 390 | }); 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /testy.json: -------------------------------------------------------------------------------- 1 | {"include":["test/**/*.spec.ts"]} 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node12/tsconfig.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "preserveConstEnums": true, 6 | "outDir": "build", 7 | "module": "commonjs", 8 | "declaration": true 9 | }, 10 | "files": [ 11 | "./index.ts" 12 | ] 13 | } 14 | --------------------------------------------------------------------------------