style: run prettier
This commit is contained in:
parent
fad2041a5e
commit
d38066a391
12
.github/ISSUE_TEMPLATE/peer-template.md
vendored
12
.github/ISSUE_TEMPLATE/peer-template.md
vendored
@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
name: peer template
|
name: peer template
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ""
|
||||||
labels: ''
|
labels: ""
|
||||||
assignees: ''
|
assignees: ""
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### I'm having an issue:
|
### I'm having an issue:
|
||||||
|
|
||||||
- Give an expressive description of what is went wrong
|
- Give an expressive description of what is went wrong
|
||||||
- Version of `peer` you're experiencing this issue
|
- Version of `peer` you're experiencing this issue
|
||||||
- Nodejs version?
|
- Nodejs version?
|
||||||
@ -16,13 +16,15 @@ assignees: ''
|
|||||||
- If you're getting an error or exception, please provide its full stack-trace as plain-text or screenshot
|
- If you're getting an error or exception, please provide its full stack-trace as plain-text or screenshot
|
||||||
|
|
||||||
### I have a suggestion:
|
### I have a suggestion:
|
||||||
|
|
||||||
- Describe your feature / request
|
- Describe your feature / request
|
||||||
- How you're going to use it? Give a usage example(s)
|
- How you're going to use it? Give a usage example(s)
|
||||||
|
|
||||||
### Documentation is missing something or incorrect (have typos, etc.):
|
### Documentation is missing something or incorrect (have typos, etc.):
|
||||||
|
|
||||||
- Give an expressive description what you have changed/added and why
|
- Give an expressive description what you have changed/added and why
|
||||||
- Make sure you're using correct markdown markup
|
- Make sure you're using correct markdown markup
|
||||||
- Make sure all code blocks starts with triple ``` (*backtick*) and have a syntax tag, for more read [this docs](https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting)
|
- Make sure all code blocks starts with triple ``` (_backtick_) and have a syntax tag, for more read [this docs](https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting)
|
||||||
- Post addition/changes in issue, we will manage it
|
- Post addition/changes in issue, we will manage it
|
||||||
|
|
||||||
## Thank you, and do not forget to get rid of this default message
|
## Thank you, and do not forget to get rid of this default message
|
||||||
|
3
.github/workflows/node.js.yml
vendored
3
.github/workflows/node.js.yml
vendored
@ -11,7 +11,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -25,7 +24,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'npm'
|
cache: "npm"
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
|
96
README.md
96
README.md
@ -4,7 +4,8 @@
|
|||||||
[](https://www.npmjs.com/package/peer)
|
[](https://www.npmjs.com/package/peer)
|
||||||
[](https://www.npmjs.com/package/peer)
|
[](https://www.npmjs.com/package/peer)
|
||||||
[](https://hub.docker.com/r/peerjs/peerjs-server)
|
[](https://hub.docker.com/r/peerjs/peerjs-server)
|
||||||
# PeerServer: A server for PeerJS #
|
|
||||||
|
# PeerServer: A server for PeerJS
|
||||||
|
|
||||||
PeerServer helps establishing connections between PeerJS clients. Data is not proxied through the server.
|
PeerServer helps establishing connections between PeerJS clients. Data is not proxied through the server.
|
||||||
|
|
||||||
@ -27,16 +28,19 @@ If you don't want to develop anything, just enter few commands below.
|
|||||||
$ npm install peer -g
|
$ npm install peer -g
|
||||||
```
|
```
|
||||||
2. Run the server:
|
2. Run the server:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ peerjs --port 9000 --key peerjs --path /myapp
|
$ peerjs --port 9000 --key peerjs --path /myapp
|
||||||
|
|
||||||
Started PeerServer on ::, port: 9000, path: /myapp (v. 0.3.2)
|
Started PeerServer on ::, port: 9000, path: /myapp (v. 0.3.2)
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Check it: http://127.0.0.1:9000/myapp It should returns JSON with name, description and website fields.
|
3. Check it: http://127.0.0.1:9000/myapp It should returns JSON with name, description and website fields.
|
||||||
|
|
||||||
#### Docker
|
#### Docker
|
||||||
|
|
||||||
Also, you can use Docker image to run a new container:
|
Also, you can use Docker image to run a new container:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ docker run -p 9000:9000 -d peerjs/peerjs-server
|
$ docker run -p 9000:9000 -d peerjs/peerjs-server
|
||||||
```
|
```
|
||||||
@ -48,9 +52,11 @@ $ kubectl run peerjs-server --image=peerjs/peerjs-server --port 9000 --expose --
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Create a custom server:
|
### Create a custom server:
|
||||||
|
|
||||||
If you have your own server, you can attach PeerServer.
|
If you have your own server, you can attach PeerServer.
|
||||||
|
|
||||||
1. Install the package:
|
1. Install the package:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# $ cd your-project-path
|
# $ cd your-project-path
|
||||||
|
|
||||||
@ -60,11 +66,13 @@ If you have your own server, you can attach PeerServer.
|
|||||||
# with yarn
|
# with yarn
|
||||||
$ yarn add peer
|
$ yarn add peer
|
||||||
```
|
```
|
||||||
2. Use PeerServer object to create a new server:
|
|
||||||
```javascript
|
|
||||||
const { PeerServer } = require('peer');
|
|
||||||
|
|
||||||
const peerServer = PeerServer({ port: 9000, path: '/myapp' });
|
2. Use PeerServer object to create a new server:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { PeerServer } = require("peer");
|
||||||
|
|
||||||
|
const peerServer = PeerServer({ port: 9000, path: "/myapp" });
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Check it: http://127.0.0.1:9000/myapp It should returns JSON with name, description and website fields.
|
3. Check it: http://127.0.0.1:9000/myapp It should returns JSON with name, description and website fields.
|
||||||
@ -73,19 +81,20 @@ If you have your own server, you can attach PeerServer.
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script>
|
<script>
|
||||||
const peer = new Peer('someid', {
|
const peer = new Peer("someid", {
|
||||||
host: 'localhost',
|
host: "localhost",
|
||||||
port: 9000,
|
port: 9000,
|
||||||
path: '/myapp'
|
path: "/myapp",
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Config / CLI options
|
## Config / CLI options
|
||||||
|
|
||||||
You can provide config object to `PeerServer` function or specify options for `peerjs` CLI.
|
You can provide config object to `PeerServer` function or specify options for `peerjs` CLI.
|
||||||
|
|
||||||
| CLI option | JS option | Description | Required | Default |
|
| CLI option | JS option | Description | Required | Default |
|
||||||
|--------------------------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:----------:|
|
| ------------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | :--------: |
|
||||||
| `--port, -p` | `port` | Port to listen (number) | **Yes** | |
|
| `--port, -p` | `port` | Port to listen (number) | **Yes** | |
|
||||||
| `--key, -k` | `key` | Connection key (string). Client must provide it to call API methods | No | `"peerjs"` |
|
| `--key, -k` | `key` | Connection key (string). Client must provide it to call API methods | No | `"peerjs"` |
|
||||||
| `--path` | `path` | Path (string). The server responds for requests to the root URL + path. **E.g.** Set the `path` to `/myapp` and run server on 9000 port via `peerjs --port 9000 --path /myapp` Then open http://127.0.0.1:9000/myapp - you should see a JSON reponse. | No | `"/"` |
|
| `--path` | `path` | Path (string). The server responds for requests to the root URL + path. **E.g.** Set the `path` to `/myapp` and run server on 9000 port via `peerjs --port 9000 --path /myapp` Then open http://127.0.0.1:9000/myapp - you should see a JSON reponse. | No | `"/"` |
|
||||||
@ -98,39 +107,40 @@ You can provide config object to `PeerServer` function or specify options for `p
|
|||||||
| `--allow_discovery` | `allow_discovery` | Allow to use GET `/peers` http API method to get an array of ids of all connected clients (boolean) | No | |
|
| `--allow_discovery` | `allow_discovery` | Allow to use GET `/peers` http API method to get an array of ids of all connected clients (boolean) | No | |
|
||||||
| `--cors` | `corsOptions` | The CORS origins that can access this server |
|
| `--cors` | `corsOptions` | The CORS origins that can access this server |
|
||||||
| | `generateClientId` | A function which generate random client IDs when calling `/id` API method (`() => string`) | No | `uuid/v4` |
|
| | `generateClientId` | A function which generate random client IDs when calling `/id` API method (`() => string`) | No | `uuid/v4` |
|
||||||
|
|
||||||
## Using HTTPS
|
## Using HTTPS
|
||||||
|
|
||||||
Simply pass in PEM-encoded certificate and key.
|
Simply pass in PEM-encoded certificate and key.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const fs = require('fs');
|
const fs = require("fs");
|
||||||
const { PeerServer } = require('peer');
|
const { PeerServer } = require("peer");
|
||||||
|
|
||||||
const peerServer = PeerServer({
|
const peerServer = PeerServer({
|
||||||
port: 9000,
|
port: 9000,
|
||||||
ssl: {
|
ssl: {
|
||||||
key: fs.readFileSync('/path/to/your/ssl/key/here.key'),
|
key: fs.readFileSync("/path/to/your/ssl/key/here.key"),
|
||||||
cert: fs.readFileSync('/path/to/your/ssl/certificate/here.crt')
|
cert: fs.readFileSync("/path/to/your/ssl/certificate/here.crt"),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pass any other [SSL options accepted by https.createServer](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistenerfrom), such as `SNICallback:
|
You can also pass any other [SSL options accepted by https.createServer](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistenerfrom), such as `SNICallback:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const fs = require('fs');
|
const fs = require("fs");
|
||||||
const { PeerServer } = require('peer');
|
const { PeerServer } = require("peer");
|
||||||
|
|
||||||
const peerServer = PeerServer({
|
const peerServer = PeerServer({
|
||||||
port: 9000,
|
port: 9000,
|
||||||
ssl: {
|
ssl: {
|
||||||
SNICallback: (servername, cb) => {
|
SNICallback: (servername, cb) => {
|
||||||
// your code here ....
|
// your code here ....
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Running PeerServer behind a reverse proxy
|
## Running PeerServer behind a reverse proxy
|
||||||
|
|
||||||
Make sure to set the `proxied` option, otherwise IP based limiting will fail.
|
Make sure to set the `proxied` option, otherwise IP based limiting will fail.
|
||||||
@ -139,29 +149,31 @@ The option is passed verbatim to the
|
|||||||
if it is truthy.
|
if it is truthy.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { PeerServer } = require('peer');
|
const { PeerServer } = require("peer");
|
||||||
|
|
||||||
const peerServer = PeerServer({
|
const peerServer = PeerServer({
|
||||||
port: 9000,
|
port: 9000,
|
||||||
path: '/myapp',
|
path: "/myapp",
|
||||||
proxied: true
|
proxied: true,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom client ID generation
|
## Custom client ID generation
|
||||||
|
|
||||||
By default, PeerServer uses `uuid/v4` npm package to generate random client IDs.
|
By default, PeerServer uses `uuid/v4` npm package to generate random client IDs.
|
||||||
|
|
||||||
You can set `generateClientId` option in config to specify a custom function to generate client IDs.
|
You can set `generateClientId` option in config to specify a custom function to generate client IDs.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { PeerServer } = require('peer');
|
const { PeerServer } = require("peer");
|
||||||
|
|
||||||
const customGenerationFunction = () => (Math.random().toString(36) + '0000000000000000000').substr(2, 16);
|
const customGenerationFunction = () =>
|
||||||
|
(Math.random().toString(36) + "0000000000000000000").substr(2, 16);
|
||||||
|
|
||||||
const peerServer = PeerServer({
|
const peerServer = PeerServer({
|
||||||
port: 9000,
|
port: 9000,
|
||||||
path: '/myapp',
|
path: "/myapp",
|
||||||
generateClientId: customGenerationFunction
|
generateClientId: customGenerationFunction,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -170,34 +182,34 @@ Open http://127.0.0.1:9000/myapp/peerjs/id to see a new random id.
|
|||||||
## Combining with existing express app
|
## Combining with existing express app
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const express = require('express');
|
const express = require("express");
|
||||||
const { ExpressPeerServer } = require('peer');
|
const { ExpressPeerServer } = require("peer");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.get('/', (req, res, next) => res.send('Hello world!'));
|
app.get("/", (req, res, next) => res.send("Hello world!"));
|
||||||
|
|
||||||
// =======
|
// =======
|
||||||
|
|
||||||
const server = app.listen(9000);
|
const server = app.listen(9000);
|
||||||
|
|
||||||
const peerServer = ExpressPeerServer(server, {
|
const peerServer = ExpressPeerServer(server, {
|
||||||
path: '/myapp'
|
path: "/myapp",
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/peerjs', peerServer);
|
app.use("/peerjs", peerServer);
|
||||||
|
|
||||||
// == OR ==
|
// == OR ==
|
||||||
|
|
||||||
const http = require('http');
|
const http = require("http");
|
||||||
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const peerServer = ExpressPeerServer(server, {
|
const peerServer = ExpressPeerServer(server, {
|
||||||
debug: true,
|
debug: true,
|
||||||
path: '/myapp'
|
path: "/myapp",
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/peerjs', peerServer);
|
app.use("/peerjs", peerServer);
|
||||||
|
|
||||||
server.listen(9000);
|
server.listen(9000);
|
||||||
|
|
||||||
@ -236,18 +248,20 @@ $ npm test
|
|||||||
We have 'ready to use' images on docker hub:
|
We have 'ready to use' images on docker hub:
|
||||||
https://hub.docker.com/r/peerjs/peerjs-server
|
https://hub.docker.com/r/peerjs/peerjs-server
|
||||||
|
|
||||||
|
|
||||||
To run the latest image:
|
To run the latest image:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ docker run -p 9000:9000 -d peerjs/peerjs-server
|
$ docker run -p 9000:9000 -d peerjs/peerjs-server
|
||||||
```
|
```
|
||||||
|
|
||||||
You can build a new image simply by calling:
|
You can build a new image simply by calling:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ docker build -t myimage https://github.com/peers/peerjs-server.git
|
$ docker build -t myimage https://github.com/peers/peerjs-server.git
|
||||||
```
|
```
|
||||||
|
|
||||||
To run the image execute this:
|
To run the image execute this:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ docker run -p 9000:9000 -d myimage
|
$ docker run -p 9000:9000 -d myimage
|
||||||
```
|
```
|
||||||
@ -289,23 +303,23 @@ resources:
|
|||||||
3. Create `server.js` (which node will run by default for the `start` script):
|
3. Create `server.js` (which node will run by default for the `start` script):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const express = require('express');
|
const express = require("express");
|
||||||
const { ExpressPeerServer } = require('peer');
|
const { ExpressPeerServer } = require("peer");
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.enable('trust proxy');
|
app.enable("trust proxy");
|
||||||
|
|
||||||
const PORT = process.env.PORT || 9000;
|
const PORT = process.env.PORT || 9000;
|
||||||
const server = app.listen(PORT, () => {
|
const server = app.listen(PORT, () => {
|
||||||
console.log(`App listening on port ${PORT}`);
|
console.log(`App listening on port ${PORT}`);
|
||||||
console.log('Press Ctrl+C to quit.');
|
console.log("Press Ctrl+C to quit.");
|
||||||
});
|
});
|
||||||
|
|
||||||
const peerServer = ExpressPeerServer(server, {
|
const peerServer = ExpressPeerServer(server, {
|
||||||
path: '/'
|
path: "/",
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/', peerServer);
|
app.use("/", peerServer);
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
```
|
```
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { Client } from '../../../../src/models/client';
|
import { Client } from "../../../../src/models/client";
|
||||||
import { HeartbeatHandler } from '../../../../src/messageHandler/handlers';
|
import { HeartbeatHandler } from "../../../../src/messageHandler/handlers";
|
||||||
|
|
||||||
describe('Heartbeat handler', () => {
|
describe("Heartbeat handler", () => {
|
||||||
it('should update last ping time', () => {
|
it("should update last ping time", () => {
|
||||||
const client = new Client({ id: 'id', token: '' });
|
const client = new Client({ id: "id", token: "" });
|
||||||
client.setLastPing(0);
|
client.setLastPing(0);
|
||||||
|
|
||||||
const nowTime = new Date().getTime();
|
const nowTime = new Date().getTime();
|
||||||
|
|
||||||
HeartbeatHandler(client);
|
HeartbeatHandler(client);
|
||||||
expect(client.getLastPing()).toBeGreaterThanOrEqual(nowTime-2)
|
expect(client.getLastPing()).toBeGreaterThanOrEqual(nowTime - 2);
|
||||||
expect(nowTime).toBeGreaterThanOrEqual(client.getLastPing())
|
expect(nowTime).toBeGreaterThanOrEqual(client.getLastPing());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { HandlersRegistry } from '../../src/messageHandler/handlersRegistry';
|
import { HandlersRegistry } from "../../src/messageHandler/handlersRegistry";
|
||||||
import type { Handler } from '../../src/messageHandler/handler';
|
import type { Handler } from "../../src/messageHandler/handler";
|
||||||
import { MessageType } from '../../src/enums';
|
import { MessageType } from "../../src/enums";
|
||||||
|
|
||||||
describe('HandlersRegistry', () => {
|
describe("HandlersRegistry", () => {
|
||||||
it('should execute handler for message type', () => {
|
it("should execute handler for message type", () => {
|
||||||
const handlersRegistry = new HandlersRegistry();
|
const handlersRegistry = new HandlersRegistry();
|
||||||
|
|
||||||
let handled = false;
|
let handled = false;
|
||||||
@ -17,7 +17,11 @@ describe('HandlersRegistry', () => {
|
|||||||
|
|
||||||
handlersRegistry.registerHandler(MessageType.OPEN, handler);
|
handlersRegistry.registerHandler(MessageType.OPEN, handler);
|
||||||
|
|
||||||
handlersRegistry.handle(undefined, { type: MessageType.OPEN, src: 'src', dst: 'dst' });
|
handlersRegistry.handle(undefined, {
|
||||||
|
type: MessageType.OPEN,
|
||||||
|
src: "src",
|
||||||
|
dst: "dst",
|
||||||
|
});
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
expect(handled).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { MessageQueue } from '../../src/models/messageQueue';
|
import { MessageQueue } from "../../src/models/messageQueue";
|
||||||
import { MessageType } from '../../src/enums';
|
import { MessageType } from "../../src/enums";
|
||||||
import type { IMessage } from '../../src/models/message';
|
import type { IMessage } from "../../src/models/message";
|
||||||
import { wait } from '../utils';
|
import { wait } from "../utils";
|
||||||
|
|
||||||
describe('MessageQueue', () => {
|
describe("MessageQueue", () => {
|
||||||
const createTestMessage = (): IMessage => {
|
const createTestMessage = (): IMessage => {
|
||||||
return {
|
return {
|
||||||
type: MessageType.OPEN,
|
type: MessageType.OPEN,
|
||||||
src: 'src',
|
src: "src",
|
||||||
dst: 'dst'
|
dst: "dst",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('#addMessage', () => {
|
describe("#addMessage", () => {
|
||||||
it('should add message to queue', () => {
|
it("should add message to queue", () => {
|
||||||
const queue = new MessageQueue();
|
const queue = new MessageQueue();
|
||||||
queue.addMessage(createTestMessage());
|
queue.addMessage(createTestMessage());
|
||||||
expect(queue.getMessages().length).toBe(1);
|
expect(queue.getMessages().length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#readMessage', () => {
|
describe("#readMessage", () => {
|
||||||
it('should return undefined for empty queue', () => {
|
it("should return undefined for empty queue", () => {
|
||||||
const queue = new MessageQueue();
|
const queue = new MessageQueue();
|
||||||
expect(queue.readMessage()).toBeUndefined();
|
expect(queue.readMessage()).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return message if any exists in queue', () => {
|
it("should return message if any exists in queue", () => {
|
||||||
const queue = new MessageQueue();
|
const queue = new MessageQueue();
|
||||||
const message = createTestMessage();
|
const message = createTestMessage();
|
||||||
queue.addMessage(message);
|
queue.addMessage(message);
|
||||||
@ -38,15 +38,15 @@ describe('MessageQueue', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getLastReadAt', () => {
|
describe("#getLastReadAt", () => {
|
||||||
it('should not be changed if no messages when read', () => {
|
it("should not be changed if no messages when read", () => {
|
||||||
const queue = new MessageQueue();
|
const queue = new MessageQueue();
|
||||||
const lastReadAt = queue.getLastReadAt();
|
const lastReadAt = queue.getLastReadAt();
|
||||||
queue.readMessage();
|
queue.readMessage();
|
||||||
expect(queue.getLastReadAt()).toBe(lastReadAt);
|
expect(queue.getLastReadAt()).toBe(lastReadAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be changed when read message', async () => {
|
it("should be changed when read message", async () => {
|
||||||
const queue = new MessageQueue();
|
const queue = new MessageQueue();
|
||||||
const lastReadAt = queue.getLastReadAt();
|
const lastReadAt = queue.getLastReadAt();
|
||||||
queue.addMessage(createTestMessage());
|
queue.addMessage(createTestMessage());
|
||||||
|
@ -1,50 +1,50 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { Realm } from '../../src/models/realm';
|
import { Realm } from "../../src/models/realm";
|
||||||
import { Client } from '../../src/models/client';
|
import { Client } from "../../src/models/client";
|
||||||
|
|
||||||
describe('Realm', () => {
|
describe("Realm", () => {
|
||||||
describe('#generateClientId', () => {
|
describe("#generateClientId", () => {
|
||||||
it('should generate a 36-character UUID, or return function value', () => {
|
it("should generate a 36-character UUID, or return function value", () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
expect(realm.generateClientId().length).toBe(36);
|
expect(realm.generateClientId().length).toBe(36);
|
||||||
expect(realm.generateClientId(() => 'abcd')).toBe('abcd');
|
expect(realm.generateClientId(() => "abcd")).toBe("abcd");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#setClient', () => {
|
describe("#setClient", () => {
|
||||||
it('should add client to realm', () => {
|
it("should add client to realm", () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const client = new Client({ id: 'id', token: '' });
|
const client = new Client({ id: "id", token: "" });
|
||||||
|
|
||||||
realm.setClient(client, 'id');
|
realm.setClient(client, "id");
|
||||||
expect(realm.getClientsIds()).toEqual(['id']);
|
expect(realm.getClientsIds()).toEqual(["id"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#removeClientById', () => {
|
describe("#removeClientById", () => {
|
||||||
it('should remove client from realm', () => {
|
it("should remove client from realm", () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const client = new Client({ id: 'id', token: '' });
|
const client = new Client({ id: "id", token: "" });
|
||||||
|
|
||||||
realm.setClient(client, 'id');
|
realm.setClient(client, "id");
|
||||||
realm.removeClientById('id');
|
realm.removeClientById("id");
|
||||||
|
|
||||||
expect(realm.getClientById('id')).toBeUndefined();
|
expect(realm.getClientById("id")).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getClientsIds', () => {
|
describe("#getClientsIds", () => {
|
||||||
it('should reflects on add/remove childs', () => {
|
it("should reflects on add/remove childs", () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const client = new Client({ id: 'id', token: '' });
|
const client = new Client({ id: "id", token: "" });
|
||||||
|
|
||||||
realm.setClient(client, 'id');
|
realm.setClient(client, "id");
|
||||||
expect(realm.getClientsIds()).toEqual(['id']);
|
expect(realm.getClientsIds()).toEqual(["id"]);
|
||||||
|
|
||||||
expect(realm.getClientById('id')).toBe(client);
|
expect(realm.getClientById("id")).toBe(client);
|
||||||
|
|
||||||
realm.removeClientById('id');
|
realm.removeClientById("id");
|
||||||
expect(realm.getClientsIds()).toEqual([]);
|
expect(realm.getClientsIds()).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,91 +1,91 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import http from 'http';
|
import http from "http";
|
||||||
import expectedJson from '../app.json';
|
import expectedJson from "../app.json";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import { startServer } from "./utils";
|
import { startServer } from "./utils";
|
||||||
|
|
||||||
const PORT = '9000';
|
const PORT = "9000";
|
||||||
|
|
||||||
async function makeRequest() {
|
async function makeRequest() {
|
||||||
return new Promise<object>((resolve, reject) => {
|
return new Promise<object>((resolve, reject) => {
|
||||||
http.get(`http://localhost:${PORT}/`, resp => {
|
http
|
||||||
let data = '';
|
.get(`http://localhost:${PORT}/`, (resp) => {
|
||||||
|
let data = "";
|
||||||
|
|
||||||
resp.on('data', chunk => {
|
resp.on("data", (chunk) => {
|
||||||
data += chunk;
|
data += chunk;
|
||||||
});
|
});
|
||||||
|
|
||||||
resp.on('end', () => {
|
resp.on("end", () => {
|
||||||
resolve(JSON.parse(data));
|
resolve(JSON.parse(data));
|
||||||
});
|
});
|
||||||
|
})
|
||||||
}).on("error", err => {
|
.on("error", (err) => {
|
||||||
console.log("Error: " + err.message);
|
console.log("Error: " + err.message);
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Check bin/peerjs', () => {
|
describe("Check bin/peerjs", () => {
|
||||||
it('should return content of app.json file', async () => {
|
it("should return content of app.json file", async () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
|
|
||||||
const ls = await startServer()
|
const ls = await startServer();
|
||||||
try {
|
try {
|
||||||
const resp = await makeRequest();
|
const resp = await makeRequest();
|
||||||
expect(resp).toEqual(expectedJson);
|
expect(resp).toEqual(expectedJson);
|
||||||
} finally {
|
} finally {
|
||||||
ls.kill();
|
ls.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reflect the origin header in CORS by default', async () => {
|
it("should reflect the origin header in CORS by default", async () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
|
|
||||||
const ls = await startServer()
|
const ls = await startServer();
|
||||||
const origin = crypto.randomUUID();
|
const origin = crypto.randomUUID();
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
const res = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
||||||
headers: {
|
headers: {
|
||||||
Origin: origin
|
Origin: origin,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
expect(res.headers.get("access-control-allow-origin")).toBe(origin)
|
expect(res.headers.get("access-control-allow-origin")).toBe(origin);
|
||||||
} finally {
|
} finally {
|
||||||
ls.kill()
|
ls.kill();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should respect the CORS parameters', async () => {
|
it("should respect the CORS parameters", async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
|
|
||||||
const origin1 = crypto.randomUUID();
|
const origin1 = crypto.randomUUID();
|
||||||
const origin2 = crypto.randomUUID();
|
const origin2 = crypto.randomUUID();
|
||||||
const origin3 = crypto.randomUUID();
|
const origin3 = crypto.randomUUID();
|
||||||
const ls = await startServer(["--cors", origin1, "--cors", origin2])
|
const ls = await startServer(["--cors", origin1, "--cors", origin2]);
|
||||||
try {
|
try {
|
||||||
const res1 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
const res1 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
||||||
headers: {
|
headers: {
|
||||||
Origin: origin1
|
Origin: origin1,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
expect(res1.headers.get("access-control-allow-origin")).toBe(origin1)
|
expect(res1.headers.get("access-control-allow-origin")).toBe(origin1);
|
||||||
const res2 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
const res2 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
||||||
headers: {
|
headers: {
|
||||||
Origin: origin2
|
Origin: origin2,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
expect(res2.headers.get("access-control-allow-origin")).toBe(origin2)
|
expect(res2.headers.get("access-control-allow-origin")).toBe(origin2);
|
||||||
const res3 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
const res3 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
|
||||||
headers: {
|
headers: {
|
||||||
Origin: origin3
|
Origin: origin3,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
expect(res3.headers.get("access-control-allow-origin")).toBe(null)
|
expect(res3.headers.get("access-control-allow-origin")).toBe(null);
|
||||||
} finally {
|
} finally {
|
||||||
ls.kill()
|
ls.kill();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,33 +1,41 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { Client } from '../../../src/models/client';
|
import { Client } from "../../../src/models/client";
|
||||||
import { Realm } from '../../../src/models/realm';
|
import { Realm } from "../../../src/models/realm";
|
||||||
import { CheckBrokenConnections } from '../../../src/services/checkBrokenConnections';
|
import { CheckBrokenConnections } from "../../../src/services/checkBrokenConnections";
|
||||||
import { wait } from '../../utils';
|
import { wait } from "../../utils";
|
||||||
|
|
||||||
describe('CheckBrokenConnections', () => {
|
describe("CheckBrokenConnections", () => {
|
||||||
it('should remove client after 2 checks', async () => {
|
it("should remove client after 2 checks", async () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const doubleCheckTime = 55; //~ equals to checkBrokenConnections.checkInterval * 2
|
const doubleCheckTime = 55; //~ equals to checkBrokenConnections.checkInterval * 2
|
||||||
const checkBrokenConnections = new CheckBrokenConnections({ realm, config: { alive_timeout: doubleCheckTime }, checkInterval: 30 });
|
const checkBrokenConnections = new CheckBrokenConnections({
|
||||||
const client = new Client({ id: 'id', token: '' });
|
realm,
|
||||||
realm.setClient(client, 'id');
|
config: { alive_timeout: doubleCheckTime },
|
||||||
|
checkInterval: 30,
|
||||||
|
});
|
||||||
|
const client = new Client({ id: "id", token: "" });
|
||||||
|
realm.setClient(client, "id");
|
||||||
|
|
||||||
checkBrokenConnections.start();
|
checkBrokenConnections.start();
|
||||||
|
|
||||||
await wait(checkBrokenConnections.checkInterval * 2 + 30);
|
await wait(checkBrokenConnections.checkInterval * 2 + 30);
|
||||||
|
|
||||||
expect(realm.getClientById('id')).toBeUndefined();
|
expect(realm.getClientById("id")).toBeUndefined();
|
||||||
|
|
||||||
checkBrokenConnections.stop();
|
checkBrokenConnections.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove client after 1 ping', async () => {
|
it("should remove client after 1 ping", async () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const doubleCheckTime = 55; //~ equals to checkBrokenConnections.checkInterval * 2
|
const doubleCheckTime = 55; //~ equals to checkBrokenConnections.checkInterval * 2
|
||||||
const checkBrokenConnections = new CheckBrokenConnections({ realm, config: { alive_timeout: doubleCheckTime }, checkInterval: 30 });
|
const checkBrokenConnections = new CheckBrokenConnections({
|
||||||
const client = new Client({ id: 'id', token: '' });
|
realm,
|
||||||
realm.setClient(client, 'id');
|
config: { alive_timeout: doubleCheckTime },
|
||||||
|
checkInterval: 30,
|
||||||
|
});
|
||||||
|
const client = new Client({ id: "id", token: "" });
|
||||||
|
realm.setClient(client, "id");
|
||||||
|
|
||||||
checkBrokenConnections.start();
|
checkBrokenConnections.start();
|
||||||
|
|
||||||
@ -38,7 +46,7 @@ describe('CheckBrokenConnections', () => {
|
|||||||
|
|
||||||
await wait(checkBrokenConnections.checkInterval * 2 + 10);
|
await wait(checkBrokenConnections.checkInterval * 2 + 10);
|
||||||
|
|
||||||
expect(realm.getClientById('id')).toBeUndefined();
|
expect(realm.getClientById("id")).toBeUndefined();
|
||||||
|
|
||||||
checkBrokenConnections.stop();
|
checkBrokenConnections.stop();
|
||||||
});
|
});
|
||||||
|
@ -1,40 +1,49 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { Client } from '../../../src/models/client';
|
import { Client } from "../../../src/models/client";
|
||||||
import { Realm } from '../../../src/models/realm';
|
import { Realm } from "../../../src/models/realm";
|
||||||
import type { IMessage } from '../../../src/models/message';
|
import type { IMessage } from "../../../src/models/message";
|
||||||
import { MessagesExpire } from '../../../src/services/messagesExpire';
|
import { MessagesExpire } from "../../../src/services/messagesExpire";
|
||||||
import { MessageHandler } from '../../../src/messageHandler';
|
import { MessageHandler } from "../../../src/messageHandler";
|
||||||
import { MessageType } from '../../../src/enums';
|
import { MessageType } from "../../../src/enums";
|
||||||
import { wait } from '../../utils';
|
import { wait } from "../../utils";
|
||||||
|
|
||||||
describe('MessagesExpire', () => {
|
describe("MessagesExpire", () => {
|
||||||
const createTestMessage = (dst: string): IMessage => {
|
const createTestMessage = (dst: string): IMessage => {
|
||||||
return {
|
return {
|
||||||
type: MessageType.OPEN,
|
type: MessageType.OPEN,
|
||||||
src: 'src',
|
src: "src",
|
||||||
dst,
|
dst,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should remove client if no read from queue', async () => {
|
it("should remove client if no read from queue", async () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const messageHandler = new MessageHandler(realm);
|
const messageHandler = new MessageHandler(realm);
|
||||||
const checkInterval = 10;
|
const checkInterval = 10;
|
||||||
const expireTimeout = 50;
|
const expireTimeout = 50;
|
||||||
const config = { cleanup_out_msgs: checkInterval, expire_timeout: expireTimeout };
|
const config = {
|
||||||
|
cleanup_out_msgs: checkInterval,
|
||||||
|
expire_timeout: expireTimeout,
|
||||||
|
};
|
||||||
|
|
||||||
const messagesExpire = new MessagesExpire({ realm, config, messageHandler });
|
const messagesExpire = new MessagesExpire({
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
messageHandler,
|
||||||
|
});
|
||||||
|
|
||||||
const client = new Client({ id: 'id', token: '' });
|
const client = new Client({ id: "id", token: "" });
|
||||||
realm.setClient(client, 'id');
|
realm.setClient(client, "id");
|
||||||
realm.addMessageToQueue(client.getId(), createTestMessage('dst'));
|
realm.addMessageToQueue(client.getId(), createTestMessage("dst"));
|
||||||
|
|
||||||
messagesExpire.startMessagesExpiration();
|
messagesExpire.startMessagesExpiration();
|
||||||
|
|
||||||
await wait(checkInterval * 2);
|
await wait(checkInterval * 2);
|
||||||
|
|
||||||
expect(realm.getMessageQueueById(client.getId())?.getMessages().length).toBe(1);
|
expect(
|
||||||
|
realm.getMessageQueueById(client.getId())?.getMessages().length,
|
||||||
|
).toBe(1);
|
||||||
|
|
||||||
await wait(expireTimeout);
|
await wait(expireTimeout);
|
||||||
|
|
||||||
@ -43,19 +52,26 @@ describe('MessagesExpire', () => {
|
|||||||
messagesExpire.stopMessagesExpiration();
|
messagesExpire.stopMessagesExpiration();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fire EXPIRE message', async () => {
|
it("should fire EXPIRE message", async () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const messageHandler = new MessageHandler(realm);
|
const messageHandler = new MessageHandler(realm);
|
||||||
const checkInterval = 10;
|
const checkInterval = 10;
|
||||||
const expireTimeout = 50;
|
const expireTimeout = 50;
|
||||||
const config = { cleanup_out_msgs: checkInterval, expire_timeout: expireTimeout };
|
const config = {
|
||||||
|
cleanup_out_msgs: checkInterval,
|
||||||
|
expire_timeout: expireTimeout,
|
||||||
|
};
|
||||||
|
|
||||||
const messagesExpire = new MessagesExpire({ realm, config, messageHandler });
|
const messagesExpire = new MessagesExpire({
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
messageHandler,
|
||||||
|
});
|
||||||
|
|
||||||
const client = new Client({ id: 'id', token: '' });
|
const client = new Client({ id: "id", token: "" });
|
||||||
realm.setClient(client, 'id');
|
realm.setClient(client, "id");
|
||||||
realm.addMessageToQueue(client.getId(), createTestMessage('dst1'));
|
realm.addMessageToQueue(client.getId(), createTestMessage("dst1"));
|
||||||
realm.addMessageToQueue(client.getId(), createTestMessage('dst2'));
|
realm.addMessageToQueue(client.getId(), createTestMessage("dst2"));
|
||||||
|
|
||||||
let handledCount = 0;
|
let handledCount = 0;
|
||||||
|
|
||||||
|
@ -1,32 +1,35 @@
|
|||||||
import { describe, expect, it } from "@jest/globals";
|
import { describe, expect, it } from "@jest/globals";
|
||||||
|
|
||||||
import { Server, WebSocket } from 'mock-socket';
|
import { Server, WebSocket } from "mock-socket";
|
||||||
import type {Server as HttpServer} from 'node:http';
|
import type { Server as HttpServer } from "node:http";
|
||||||
import { Realm } from '../../../src/models/realm';
|
import { Realm } from "../../../src/models/realm";
|
||||||
import { WebSocketServer } from '../../../src/services/webSocketServer';
|
import { WebSocketServer } from "../../../src/services/webSocketServer";
|
||||||
import { Errors, MessageType } from '../../../src/enums';
|
import { Errors, MessageType } from "../../../src/enums";
|
||||||
import { wait } from '../../utils';
|
import { wait } from "../../utils";
|
||||||
|
|
||||||
type Destroyable<T> = T & { destroy?: () => Promise<void>; };
|
type Destroyable<T> = T & { destroy?: () => Promise<void> };
|
||||||
|
|
||||||
const checkOpen = async (c: WebSocket): Promise<boolean> => {
|
const checkOpen = async (c: WebSocket): Promise<boolean> => {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
c.onmessage = (event: object & { data?: string; }): void => {
|
c.onmessage = (event: object & { data?: string }): void => {
|
||||||
const message = JSON.parse(event.data as string);
|
const message = JSON.parse(event.data as string);
|
||||||
resolve(message.type === MessageType.OPEN);
|
resolve(message.type === MessageType.OPEN);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkSequence = async (c: WebSocket, msgs: { type: MessageType; error?: Errors; }[]): Promise<boolean> => {
|
const checkSequence = async (
|
||||||
return new Promise(resolve => {
|
c: WebSocket,
|
||||||
|
msgs: { type: MessageType; error?: Errors }[],
|
||||||
|
): Promise<boolean> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
const restMessages = [...msgs];
|
const restMessages = [...msgs];
|
||||||
|
|
||||||
const finish = (success = false): void => {
|
const finish = (success = false): void => {
|
||||||
resolve(success);
|
resolve(success);
|
||||||
};
|
};
|
||||||
|
|
||||||
c.onmessage = (event: object & { data?: string; }): void => {
|
c.onmessage = (event: object & { data?: string }): void => {
|
||||||
const [mes] = restMessages;
|
const [mes] = restMessages;
|
||||||
|
|
||||||
if (!mes) {
|
if (!mes) {
|
||||||
@ -53,16 +56,38 @@ const checkSequence = async (c: WebSocket, msgs: { type: MessageType; error?: Er
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTestServer = ({ realm, config, url }: { realm: Realm; config: { path: string; key: string; concurrent_limit: number; }; url: string; }): Destroyable<WebSocketServer> => {
|
const createTestServer = ({
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
url,
|
||||||
|
}: {
|
||||||
|
realm: Realm;
|
||||||
|
config: { path: string; key: string; concurrent_limit: number };
|
||||||
|
url: string;
|
||||||
|
}): Destroyable<WebSocketServer> => {
|
||||||
const server = new Server(url) as Server & HttpServer;
|
const server = new Server(url) as Server & HttpServer;
|
||||||
const webSocketServer: Destroyable<WebSocketServer> = new WebSocketServer({ server, realm, config });
|
const webSocketServer: Destroyable<WebSocketServer> = new WebSocketServer({
|
||||||
|
server,
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
server.on('connection', (socket: WebSocket & { on?: (eventName: string, callback: () => void) => void; }) => {
|
server.on(
|
||||||
|
"connection",
|
||||||
|
(
|
||||||
|
socket: WebSocket & {
|
||||||
|
on?: (eventName: string, callback: () => void) => void;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
const s = webSocketServer.socketServer;
|
const s = webSocketServer.socketServer;
|
||||||
s.emit('connection', socket, { url: socket.url });
|
s.emit("connection", socket, { url: socket.url });
|
||||||
|
|
||||||
socket.onclose = (): void => {
|
socket.onclose = (): void => {
|
||||||
const userId = socket.url.split('?')[1]?.split('&').find(p => p.startsWith('id'))?.split('=')[1];
|
const userId = socket.url
|
||||||
|
.split("?")[1]
|
||||||
|
?.split("&")
|
||||||
|
.find((p) => p.startsWith("id"))
|
||||||
|
?.split("=")[1];
|
||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
@ -72,11 +97,17 @@ const createTestServer = ({ realm, config, url }: { realm: Realm; config: { path
|
|||||||
|
|
||||||
if (!clientSocket) return;
|
if (!clientSocket) return;
|
||||||
|
|
||||||
(clientSocket as unknown as WebSocket).listeners['server::close']?.forEach((s: () => void) => s());
|
(clientSocket as unknown as WebSocket).listeners[
|
||||||
|
"server::close"
|
||||||
|
]?.forEach((s: () => void) => s());
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onmessage = (event: object & { data?: string; }): void => {
|
socket.onmessage = (event: object & { data?: string }): void => {
|
||||||
const userId = socket.url.split('?')[1]?.split('&').find(p => p.startsWith('id'))?.split('=')[1];
|
const userId = socket.url
|
||||||
|
.split("?")[1]
|
||||||
|
?.split("&")
|
||||||
|
.find((p) => p.startsWith("id"))
|
||||||
|
?.split("=")[1];
|
||||||
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
@ -86,9 +117,12 @@ const createTestServer = ({ realm, config, url }: { realm: Realm; config: { path
|
|||||||
|
|
||||||
if (!clientSocket) return;
|
if (!clientSocket) return;
|
||||||
|
|
||||||
(clientSocket as unknown as WebSocket).listeners['server::message']?.forEach((s: (data: object) => void) => s(event));
|
(clientSocket as unknown as WebSocket).listeners[
|
||||||
|
"server::message"
|
||||||
|
]?.forEach((s: (data: object) => void) => s(event));
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
webSocketServer.destroy = async (): Promise<void> => {
|
webSocketServer.destroy = async (): Promise<void> => {
|
||||||
server.close();
|
server.close();
|
||||||
@ -97,22 +131,25 @@ const createTestServer = ({ realm, config, url }: { realm: Realm; config: { path
|
|||||||
return webSocketServer;
|
return webSocketServer;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('WebSocketServer', () => {
|
describe("WebSocketServer", () => {
|
||||||
|
it("should return valid path", () => {
|
||||||
it('should return valid path', () => {
|
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const config = { path: '/', key: 'testKey', concurrent_limit: 1 };
|
const config = { path: "/", key: "testKey", concurrent_limit: 1 };
|
||||||
const config2 = { ...config, path: 'path' };
|
const config2 = { ...config, path: "path" };
|
||||||
const server = new Server('path1') as Server & HttpServer;
|
const server = new Server("path1") as Server & HttpServer;
|
||||||
const server2 = new Server('path2') as Server & HttpServer;
|
const server2 = new Server("path2") as Server & HttpServer;
|
||||||
|
|
||||||
const webSocketServer = new WebSocketServer({ server, realm, config });
|
const webSocketServer = new WebSocketServer({ server, realm, config });
|
||||||
|
|
||||||
expect(webSocketServer.path).toBe('/peerjs');
|
expect(webSocketServer.path).toBe("/peerjs");
|
||||||
|
|
||||||
const webSocketServer2 = new WebSocketServer({ server: server2, realm, config: config2 });
|
const webSocketServer2 = new WebSocketServer({
|
||||||
|
server: server2,
|
||||||
|
realm,
|
||||||
|
config: config2,
|
||||||
|
});
|
||||||
|
|
||||||
expect(webSocketServer2.path).toBe('path/peerjs');
|
expect(webSocketServer2.path).toBe("path/peerjs");
|
||||||
|
|
||||||
server.stop();
|
server.stop();
|
||||||
server2.stop();
|
server2.stop();
|
||||||
@ -120,15 +157,20 @@ describe('WebSocketServer', () => {
|
|||||||
|
|
||||||
it(`should check client's params`, async () => {
|
it(`should check client's params`, async () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const config = { path: '/', key: 'testKey', concurrent_limit: 1 };
|
const config = { path: "/", key: "testKey", concurrent_limit: 1 };
|
||||||
const fakeURL = 'ws://localhost:8080/peerjs';
|
const fakeURL = "ws://localhost:8080/peerjs";
|
||||||
|
|
||||||
const getError = async (url: string, validError: Errors = Errors.INVALID_WS_PARAMETERS): Promise<boolean> => {
|
const getError = async (
|
||||||
|
url: string,
|
||||||
|
validError: Errors = Errors.INVALID_WS_PARAMETERS,
|
||||||
|
): Promise<boolean> => {
|
||||||
const webSocketServer = createTestServer({ url, realm, config });
|
const webSocketServer = createTestServer({ url, realm, config });
|
||||||
|
|
||||||
const ws = new WebSocket(url);
|
const ws = new WebSocket(url);
|
||||||
|
|
||||||
const errorSent = await checkSequence(ws, [{ type: MessageType.ERROR, error: validError }]);
|
const errorSent = await checkSequence(ws, [
|
||||||
|
{ type: MessageType.ERROR, error: validError },
|
||||||
|
]);
|
||||||
|
|
||||||
ws.close();
|
ws.close();
|
||||||
|
|
||||||
@ -140,13 +182,18 @@ describe('WebSocketServer', () => {
|
|||||||
expect(await getError(fakeURL)).toBe(true);
|
expect(await getError(fakeURL)).toBe(true);
|
||||||
expect(await getError(`${fakeURL}?key=${config.key}`)).toBe(true);
|
expect(await getError(`${fakeURL}?key=${config.key}`)).toBe(true);
|
||||||
expect(await getError(`${fakeURL}?key=${config.key}&id=1`)).toBe(true);
|
expect(await getError(`${fakeURL}?key=${config.key}&id=1`)).toBe(true);
|
||||||
expect(await getError(`${fakeURL}?key=notValidKey&id=userId&token=userToken`, Errors.INVALID_KEY)).toBe(true);
|
expect(
|
||||||
|
await getError(
|
||||||
|
`${fakeURL}?key=notValidKey&id=userId&token=userToken`,
|
||||||
|
Errors.INVALID_KEY,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should check concurrent limit`, async () => {
|
it(`should check concurrent limit`, async () => {
|
||||||
const realm = new Realm();
|
const realm = new Realm();
|
||||||
const config = { path: '/', key: 'testKey', concurrent_limit: 1 };
|
const config = { path: "/", key: "testKey", concurrent_limit: 1 };
|
||||||
const fakeURL = 'ws://localhost:8080/peerjs';
|
const fakeURL = "ws://localhost:8080/peerjs";
|
||||||
|
|
||||||
const createClient = (id: string): Destroyable<WebSocket> => {
|
const createClient = (id: string): Destroyable<WebSocket> => {
|
||||||
// id in the path ensures that all mock servers listen on different urls
|
// id in the path ensures that all mock servers listen on different urls
|
||||||
@ -169,16 +216,17 @@ describe('WebSocketServer', () => {
|
|||||||
return ws;
|
return ws;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const c1 = createClient("1");
|
||||||
const c1 = createClient('1');
|
|
||||||
|
|
||||||
expect(await checkOpen(c1)).toBe(true);
|
expect(await checkOpen(c1)).toBe(true);
|
||||||
|
|
||||||
const c2 = createClient('2');
|
const c2 = createClient("2");
|
||||||
|
|
||||||
expect(await checkSequence(c2, [
|
expect(
|
||||||
{ type: MessageType.ERROR, error: Errors.CONNECTION_LIMIT_EXCEED }
|
await checkSequence(c2, [
|
||||||
])).toBe(true);
|
{ type: MessageType.ERROR, error: Errors.CONNECTION_LIMIT_EXCEED },
|
||||||
|
]),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
await c1.destroy?.();
|
await c1.destroy?.();
|
||||||
await c2.destroy?.();
|
await c2.destroy?.();
|
||||||
@ -187,7 +235,7 @@ describe('WebSocketServer', () => {
|
|||||||
|
|
||||||
expect(realm.getClientsIds().length).toBe(0);
|
expect(realm.getClientsIds().length).toBe(0);
|
||||||
|
|
||||||
const c3 = createClient('3');
|
const c3 = createClient("3");
|
||||||
|
|
||||||
expect(await checkOpen(c3)).toBe(true);
|
expect(await checkOpen(c3)).toBe(true);
|
||||||
|
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
|
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export const wait = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
|
export const wait = (ms: number): Promise<void> =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
export const startServer = (params: string[] = []) => {
|
export const startServer = (params: string[] = []) => {
|
||||||
return new Promise<ChildProcessWithoutNullStreams>((resolve, reject) => {
|
return new Promise<ChildProcessWithoutNullStreams>((resolve, reject) => {
|
||||||
const ls = spawn('node', [path.join(__dirname, '../', 'dist/bin/peerjs.js'), '--port', "9000", ...params]);
|
const ls = spawn("node", [
|
||||||
ls.stdout.once("data", ()=> resolve(ls))
|
path.join(__dirname, "../", "dist/bin/peerjs.js"),
|
||||||
|
"--port",
|
||||||
|
"9000",
|
||||||
|
...params,
|
||||||
|
]);
|
||||||
|
ls.stdout.once("data", () => resolve(ls));
|
||||||
ls.stderr.once("data", () => {
|
ls.stderr.once("data", () => {
|
||||||
ls.kill()
|
ls.kill();
|
||||||
reject()
|
reject();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
@ -5,14 +5,14 @@ import {version} from "../package.json";
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
const optimistUsageLength = 98;
|
const optimistUsageLength = 98;
|
||||||
import yargs from "yargs";
|
import yargs from "yargs";
|
||||||
import { hideBin } from 'yargs/helpers'
|
import { hideBin } from "yargs/helpers";
|
||||||
import { PeerServer } from "../src";
|
import { PeerServer } from "../src";
|
||||||
import type { AddressInfo } from "node:net";
|
import type { AddressInfo } from "node:net";
|
||||||
import type { CorsOptions } from "cors";
|
import type { CorsOptions } from "cors";
|
||||||
|
|
||||||
const y = yargs(hideBin(process.argv));
|
const y = yargs(hideBin(process.argv));
|
||||||
|
|
||||||
const portEnvIsSet = !!process.env["PORT"]
|
const portEnvIsSet = !!process.env["PORT"];
|
||||||
|
|
||||||
const opts = y
|
const opts = y
|
||||||
.usage("Usage: $0")
|
.usage("Usage: $0")
|
||||||
@ -86,14 +86,15 @@ const opts = y
|
|||||||
describe: "Set the list of CORS origins",
|
describe: "Set the list of CORS origins",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.boolean("allow_discovery").parseSync();
|
.boolean("allow_discovery")
|
||||||
|
.parseSync();
|
||||||
|
|
||||||
if (!opts.port) {
|
if (!opts.port) {
|
||||||
opts.port= parseInt(process.env["PORT"] as string)
|
opts.port = parseInt(process.env["PORT"] as string);
|
||||||
}
|
}
|
||||||
if (opts.cors) {
|
if (opts.cors) {
|
||||||
opts["corsOptions"] = {
|
opts["corsOptions"] = {
|
||||||
origin: opts.cors
|
origin: opts.cors,
|
||||||
} satisfies CorsOptions;
|
} satisfies CorsOptions;
|
||||||
}
|
}
|
||||||
process.on("uncaughtException", function (e) {
|
process.on("uncaughtException", function (e) {
|
||||||
@ -109,7 +110,7 @@ if (opts.sslkey || opts.sslcert) {
|
|||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"Warning: PeerServer will not run because either " +
|
"Warning: PeerServer will not run because either " +
|
||||||
"the key or the certificate has not been provided."
|
"the key or the certificate has not been provided.",
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@ -124,7 +125,7 @@ const server = PeerServer(opts, (server) => {
|
|||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
userPath || "/",
|
userPath || "/",
|
||||||
version
|
version,
|
||||||
);
|
);
|
||||||
|
|
||||||
const shutdownApp = () => {
|
const shutdownApp = () => {
|
||||||
|
@ -7,7 +7,7 @@ const config = {
|
|||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
// "node_modules"
|
// "node_modules"
|
||||||
],
|
],
|
||||||
collectCoverageFrom: ["./src/**"]
|
collectCoverageFrom: ["./src/**"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -8,8 +8,9 @@ So, the base path should be like `http://127.0.0.1:9000/` or `http://127.0.0.1:9
|
|||||||
|
|
||||||
Endpoints:
|
Endpoints:
|
||||||
|
|
||||||
* GET `/` - return a JSON to test the server.
|
- GET `/` - return a JSON to test the server.
|
||||||
|
|
||||||
This group of methods uses `:key` option from config:
|
This group of methods uses `:key` option from config:
|
||||||
* GET `/:key/id` - return a new user id. required `:key` from config.
|
|
||||||
* GET `/:key/peers` - return an array of all connected users. required `:key` from config. **IMPORTANT:** You should set `allow_discovery` to `true` in config to enable this method. It disabled by default.
|
- GET `/:key/id` - return a new user id. required `:key` from config.
|
||||||
|
- GET `/:key/peers` - return an array of all connected users. required `:key` from config. **IMPORTANT:** You should set `allow_discovery` to `true` in config to enable this method. It disabled by default.
|
||||||
|
@ -5,7 +5,11 @@ import PublicApi from "./v1/public";
|
|||||||
import type { IConfig } from "../config";
|
import type { IConfig } from "../config";
|
||||||
import type { IRealm } from "../models/realm";
|
import type { IRealm } from "../models/realm";
|
||||||
|
|
||||||
export const Api = ({ config, realm, corsOptions }: {
|
export const Api = ({
|
||||||
|
config,
|
||||||
|
realm,
|
||||||
|
corsOptions,
|
||||||
|
}: {
|
||||||
config: IConfig;
|
config: IConfig;
|
||||||
realm: IRealm;
|
realm: IRealm;
|
||||||
corsOptions: CorsOptions;
|
corsOptions: CorsOptions;
|
||||||
|
@ -2,8 +2,12 @@ import express from "express";
|
|||||||
import type { IConfig } from "../../../config";
|
import type { IConfig } from "../../../config";
|
||||||
import type { IRealm } from "../../../models/realm";
|
import type { IRealm } from "../../../models/realm";
|
||||||
|
|
||||||
export default ({ config, realm }: {
|
export default ({
|
||||||
config: IConfig; realm: IRealm;
|
config,
|
||||||
|
realm,
|
||||||
|
}: {
|
||||||
|
config: IConfig;
|
||||||
|
realm: IRealm;
|
||||||
}): express.Router => {
|
}): express.Router => {
|
||||||
const app = express.Router();
|
const app = express.Router();
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type {WebSocketServer, ServerOptions} from 'ws';
|
import type { WebSocketServer, ServerOptions } from "ws";
|
||||||
import type { CorsOptions } from "cors";
|
import type { CorsOptions } from "cors";
|
||||||
|
|
||||||
export interface IConfig {
|
export interface IConfig {
|
||||||
|
@ -2,7 +2,7 @@ export enum Errors {
|
|||||||
INVALID_KEY = "Invalid key provided",
|
INVALID_KEY = "Invalid key provided",
|
||||||
INVALID_TOKEN = "Invalid token provided",
|
INVALID_TOKEN = "Invalid token provided",
|
||||||
INVALID_WS_PARAMETERS = "No id, token, or key supplied to websocket server",
|
INVALID_WS_PARAMETERS = "No id, token, or key supplied to websocket server",
|
||||||
CONNECTION_LIMIT_EXCEED = "Server has reached its concurrent user limit"
|
CONNECTION_LIMIT_EXCEED = "Server has reached its concurrent user limit",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MessageType {
|
export enum MessageType {
|
||||||
@ -14,5 +14,5 @@ export enum MessageType {
|
|||||||
EXPIRE = "EXPIRE",
|
EXPIRE = "EXPIRE",
|
||||||
HEARTBEAT = "HEARTBEAT",
|
HEARTBEAT = "HEARTBEAT",
|
||||||
ID_TAKEN = "ID-TAKEN",
|
ID_TAKEN = "ID-TAKEN",
|
||||||
ERROR = "ERROR"
|
ERROR = "ERROR",
|
||||||
}
|
}
|
||||||
|
35
src/index.ts
35
src/index.ts
@ -9,39 +9,49 @@ import {createInstance} from "./instance";
|
|||||||
import type { IClient } from "./models/client";
|
import type { IClient } from "./models/client";
|
||||||
import type { IMessage } from "./models/message";
|
import type { IMessage } from "./models/message";
|
||||||
|
|
||||||
export type {MessageType} from "./enums"
|
export type { MessageType } from "./enums";
|
||||||
export type {IConfig, PeerServerEvents, IClient, IMessage}
|
export type { IConfig, PeerServerEvents, IClient, IMessage };
|
||||||
|
|
||||||
function ExpressPeerServer(server: https.Server | http.Server, options?: Partial<IConfig>) {
|
function ExpressPeerServer(
|
||||||
|
server: https.Server | http.Server,
|
||||||
|
options?: Partial<IConfig>,
|
||||||
|
) {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const newOptions: IConfig = {
|
const newOptions: IConfig = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...options
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (newOptions.proxied) {
|
if (newOptions.proxied) {
|
||||||
app.set("trust proxy", newOptions.proxied === "false" ? false : !!newOptions.proxied);
|
app.set(
|
||||||
|
"trust proxy",
|
||||||
|
newOptions.proxied === "false" ? false : !!newOptions.proxied,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on("mount", () => {
|
app.on("mount", () => {
|
||||||
if (!server) {
|
if (!server) {
|
||||||
throw new Error("Server is not passed to constructor - " +
|
throw new Error(
|
||||||
"can't start PeerServer");
|
"Server is not passed to constructor - " + "can't start PeerServer",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createInstance({ app, server, options: newOptions });
|
createInstance({ app, server, options: newOptions });
|
||||||
});
|
});
|
||||||
|
|
||||||
return app as Express & PeerServerEvents
|
return app as Express & PeerServerEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PeerServer(options: Partial<IConfig> = {}, callback?: (server: https.Server | http.Server) => void) {
|
function PeerServer(
|
||||||
|
options: Partial<IConfig> = {},
|
||||||
|
callback?: (server: https.Server | http.Server) => void,
|
||||||
|
) {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
let newOptions: IConfig = {
|
let newOptions: IConfig = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...options
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
const port = newOptions.port;
|
const port = newOptions.port;
|
||||||
@ -66,7 +76,4 @@ function PeerServer(options: Partial<IConfig> = {}, callback?: (server: https.Se
|
|||||||
return peerjs;
|
return peerjs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { ExpressPeerServer, PeerServer };
|
||||||
ExpressPeerServer,
|
|
||||||
PeerServer
|
|
||||||
};
|
|
||||||
|
@ -16,13 +16,20 @@ import type {IMessage} from "./models/message";
|
|||||||
import type { IConfig } from "./config";
|
import type { IConfig } from "./config";
|
||||||
|
|
||||||
export interface PeerServerEvents {
|
export interface PeerServerEvents {
|
||||||
on(event: 'connection', listener: (client: IClient) => void): this;
|
on(event: "connection", listener: (client: IClient) => void): this;
|
||||||
on(event: "message", listener: (client: IClient, message: IMessage) => void): this;
|
on(
|
||||||
|
event: "message",
|
||||||
|
listener: (client: IClient, message: IMessage) => void,
|
||||||
|
): this;
|
||||||
on(event: "disconnect", listener: (client: IClient) => void): this;
|
on(event: "disconnect", listener: (client: IClient) => void): this;
|
||||||
on(event: "error", listener: (client: Error) => void): this;
|
on(event: "error", listener: (client: Error) => void): this;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createInstance = ({ app, server, options }: {
|
export const createInstance = ({
|
||||||
|
app,
|
||||||
|
server,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
app: express.Application;
|
app: express.Application;
|
||||||
server: HttpServer | HttpsServer;
|
server: HttpServer | HttpsServer;
|
||||||
options: IConfig;
|
options: IConfig;
|
||||||
@ -32,24 +39,31 @@ export const createInstance = ({ app, server, options }: {
|
|||||||
const messageHandler = new MessageHandler(realm);
|
const messageHandler = new MessageHandler(realm);
|
||||||
|
|
||||||
const api = Api({ config, realm, corsOptions: options.corsOptions });
|
const api = Api({ config, realm, corsOptions: options.corsOptions });
|
||||||
const messagesExpire: IMessagesExpire = new MessagesExpire({ realm, config, messageHandler });
|
const messagesExpire: IMessagesExpire = new MessagesExpire({
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
messageHandler,
|
||||||
|
});
|
||||||
const checkBrokenConnections = new CheckBrokenConnections({
|
const checkBrokenConnections = new CheckBrokenConnections({
|
||||||
realm,
|
realm,
|
||||||
config,
|
config,
|
||||||
onClose: client => {
|
onClose: (client) => {
|
||||||
app.emit("disconnect", client);
|
app.emit("disconnect", client);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(options.path, api);
|
app.use(options.path, api);
|
||||||
|
|
||||||
//use mountpath for WS server
|
//use mountpath for WS server
|
||||||
const customConfig = { ...config, path: path.posix.join(app.path(), options.path, '/') };
|
const customConfig = {
|
||||||
|
...config,
|
||||||
|
path: path.posix.join(app.path(), options.path, "/"),
|
||||||
|
};
|
||||||
|
|
||||||
const wss: IWebSocketServer = new WebSocketServer({
|
const wss: IWebSocketServer = new WebSocketServer({
|
||||||
server,
|
server,
|
||||||
realm,
|
realm,
|
||||||
config: customConfig
|
config: customConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
wss.on("connection", (client: IClient) => {
|
wss.on("connection", (client: IClient) => {
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import type { IClient } from "../models/client";
|
import type { IClient } from "../models/client";
|
||||||
import type { IMessage } from "../models/message";
|
import type { IMessage } from "../models/message";
|
||||||
|
|
||||||
export type Handler = (client: IClient | undefined, message: IMessage) => boolean;
|
export type Handler = (
|
||||||
|
client: IClient | undefined,
|
||||||
|
message: IMessage,
|
||||||
|
) => boolean;
|
||||||
|
@ -3,7 +3,11 @@ import type {IClient} from "../../../models/client";
|
|||||||
import type { IMessage } from "../../../models/message";
|
import type { IMessage } from "../../../models/message";
|
||||||
import type { IRealm } from "../../../models/realm";
|
import type { IRealm } from "../../../models/realm";
|
||||||
|
|
||||||
export const TransmissionHandler = ({ realm }: { realm: IRealm; }): (client: IClient | undefined, message: IMessage) => boolean => {
|
export const TransmissionHandler = ({
|
||||||
|
realm,
|
||||||
|
}: {
|
||||||
|
realm: IRealm;
|
||||||
|
}): ((client: IClient | undefined, message: IMessage) => boolean) => {
|
||||||
const handle = (client: IClient | undefined, message: IMessage) => {
|
const handle = (client: IClient | undefined, message: IMessage) => {
|
||||||
const type = message.type;
|
const type = message.type;
|
||||||
const srcId = message.src;
|
const srcId = message.src;
|
||||||
@ -36,7 +40,7 @@ export const TransmissionHandler = ({ realm }: { realm: IRealm; }): (client: ICl
|
|||||||
handle(client, {
|
handle(client, {
|
||||||
type: MessageType.LEAVE,
|
type: MessageType.LEAVE,
|
||||||
src: dstId,
|
src: dstId,
|
||||||
dst: srcId
|
dst: srcId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -12,11 +12,17 @@ export interface IMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MessageHandler implements IMessageHandler {
|
export class MessageHandler implements IMessageHandler {
|
||||||
constructor(realm: IRealm, private readonly handlersRegistry: IHandlersRegistry = new HandlersRegistry()) {
|
constructor(
|
||||||
|
realm: IRealm,
|
||||||
|
private readonly handlersRegistry: IHandlersRegistry = new HandlersRegistry(),
|
||||||
|
) {
|
||||||
const transmissionHandler: Handler = TransmissionHandler({ realm });
|
const transmissionHandler: Handler = TransmissionHandler({ realm });
|
||||||
const heartbeatHandler: Handler = HeartbeatHandler;
|
const heartbeatHandler: Handler = HeartbeatHandler;
|
||||||
|
|
||||||
const handleTransmission: Handler = (client: IClient | undefined, { type, src, dst, payload }: IMessage): boolean => {
|
const handleTransmission: Handler = (
|
||||||
|
client: IClient | undefined,
|
||||||
|
{ type, src, dst, payload }: IMessage,
|
||||||
|
): boolean => {
|
||||||
return transmissionHandler(client, {
|
return transmissionHandler(client, {
|
||||||
type,
|
type,
|
||||||
src,
|
src,
|
||||||
@ -25,14 +31,33 @@ export class MessageHandler implements IMessageHandler {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHeartbeat = (client: IClient | undefined, message: IMessage) => heartbeatHandler(client, message);
|
const handleHeartbeat = (client: IClient | undefined, message: IMessage) =>
|
||||||
|
heartbeatHandler(client, message);
|
||||||
|
|
||||||
this.handlersRegistry.registerHandler(MessageType.HEARTBEAT, handleHeartbeat);
|
this.handlersRegistry.registerHandler(
|
||||||
this.handlersRegistry.registerHandler(MessageType.OFFER, handleTransmission);
|
MessageType.HEARTBEAT,
|
||||||
this.handlersRegistry.registerHandler(MessageType.ANSWER, handleTransmission);
|
handleHeartbeat,
|
||||||
this.handlersRegistry.registerHandler(MessageType.CANDIDATE, handleTransmission);
|
);
|
||||||
this.handlersRegistry.registerHandler(MessageType.LEAVE, handleTransmission);
|
this.handlersRegistry.registerHandler(
|
||||||
this.handlersRegistry.registerHandler(MessageType.EXPIRE, handleTransmission);
|
MessageType.OFFER,
|
||||||
|
handleTransmission,
|
||||||
|
);
|
||||||
|
this.handlersRegistry.registerHandler(
|
||||||
|
MessageType.ANSWER,
|
||||||
|
handleTransmission,
|
||||||
|
);
|
||||||
|
this.handlersRegistry.registerHandler(
|
||||||
|
MessageType.CANDIDATE,
|
||||||
|
handleTransmission,
|
||||||
|
);
|
||||||
|
this.handlersRegistry.registerHandler(
|
||||||
|
MessageType.LEAVE,
|
||||||
|
handleTransmission,
|
||||||
|
);
|
||||||
|
this.handlersRegistry.registerHandler(
|
||||||
|
MessageType.EXPIRE,
|
||||||
|
handleTransmission,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handle(client: IClient | undefined, message: IMessage): boolean {
|
public handle(client: IClient | undefined, message: IMessage): boolean {
|
||||||
|
@ -22,7 +22,7 @@ export class Client implements IClient {
|
|||||||
private socket: WebSocket | null = null;
|
private socket: WebSocket | null = null;
|
||||||
private lastPing: number = new Date().getTime();
|
private lastPing: number = new Date().getTime();
|
||||||
|
|
||||||
constructor({ id, token }: { id: string; token: string; }) {
|
constructor({ id, token }: { id: string; token: string }) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,21 @@ import type {IRealm} from "../../models/realm";
|
|||||||
|
|
||||||
const DEFAULT_CHECK_INTERVAL = 300;
|
const DEFAULT_CHECK_INTERVAL = 300;
|
||||||
|
|
||||||
type CustomConfig = Pick<IConfig, 'alive_timeout'>;
|
type CustomConfig = Pick<IConfig, "alive_timeout">;
|
||||||
|
|
||||||
export class CheckBrokenConnections {
|
export class CheckBrokenConnections {
|
||||||
|
|
||||||
public readonly checkInterval: number;
|
public readonly checkInterval: number;
|
||||||
private timeoutId: NodeJS.Timeout | null = null;
|
private timeoutId: NodeJS.Timeout | null = null;
|
||||||
private readonly realm: IRealm;
|
private readonly realm: IRealm;
|
||||||
private readonly config: CustomConfig;
|
private readonly config: CustomConfig;
|
||||||
private readonly onClose?: (client: IClient) => void;
|
private readonly onClose?: (client: IClient) => void;
|
||||||
|
|
||||||
constructor({ realm, config, checkInterval = DEFAULT_CHECK_INTERVAL, onClose }: {
|
constructor({
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
checkInterval = DEFAULT_CHECK_INTERVAL,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
realm: IRealm;
|
realm: IRealm;
|
||||||
config: CustomConfig;
|
config: CustomConfig;
|
||||||
checkInterval?: number;
|
checkInterval?: number;
|
||||||
|
@ -8,7 +8,7 @@ export interface IMessagesExpire {
|
|||||||
stopMessagesExpiration(): void;
|
stopMessagesExpiration(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomConfig = Pick<IConfig, 'cleanup_out_msgs' | 'expire_timeout'>;
|
type CustomConfig = Pick<IConfig, "cleanup_out_msgs" | "expire_timeout">;
|
||||||
|
|
||||||
export class MessagesExpire implements IMessagesExpire {
|
export class MessagesExpire implements IMessagesExpire {
|
||||||
private readonly realm: IRealm;
|
private readonly realm: IRealm;
|
||||||
@ -17,7 +17,11 @@ export class MessagesExpire implements IMessagesExpire {
|
|||||||
|
|
||||||
private timeoutId: NodeJS.Timeout | null = null;
|
private timeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
constructor({ realm, config, messageHandler }: {
|
constructor({
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
messageHandler,
|
||||||
|
}: {
|
||||||
realm: IRealm;
|
realm: IRealm;
|
||||||
config: CustomConfig;
|
config: CustomConfig;
|
||||||
messageHandler: IMessageHandler;
|
messageHandler: IMessageHandler;
|
||||||
|
@ -21,18 +21,28 @@ interface IAuthParams {
|
|||||||
key?: string;
|
key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomConfig = Pick<IConfig, 'path' | 'key' | 'concurrent_limit' | 'createWebSocketServer'>;
|
type CustomConfig = Pick<
|
||||||
|
IConfig,
|
||||||
|
"path" | "key" | "concurrent_limit" | "createWebSocketServer"
|
||||||
|
>;
|
||||||
|
|
||||||
const WS_PATH = 'peerjs';
|
const WS_PATH = "peerjs";
|
||||||
|
|
||||||
export class WebSocketServer extends EventEmitter implements IWebSocketServer {
|
export class WebSocketServer extends EventEmitter implements IWebSocketServer {
|
||||||
|
|
||||||
public readonly path: string;
|
public readonly path: string;
|
||||||
private readonly realm: IRealm;
|
private readonly realm: IRealm;
|
||||||
private readonly config: CustomConfig;
|
private readonly config: CustomConfig;
|
||||||
public readonly socketServer: Server;
|
public readonly socketServer: Server;
|
||||||
|
|
||||||
constructor({ server, realm, config }: { server: HttpServer | HttpsServer; realm: IRealm; config: CustomConfig; }) {
|
constructor({
|
||||||
|
server,
|
||||||
|
realm,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
server: HttpServer | HttpsServer;
|
||||||
|
realm: IRealm;
|
||||||
|
config: CustomConfig;
|
||||||
|
}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.setMaxListeners(0);
|
this.setMaxListeners(0);
|
||||||
@ -41,28 +51,28 @@ export class WebSocketServer extends EventEmitter implements IWebSocketServer {
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
const path = this.config.path;
|
const path = this.config.path;
|
||||||
this.path = `${path}${path.endsWith('/') ? "" : "/"}${WS_PATH}`;
|
this.path = `${path}${path.endsWith("/") ? "" : "/"}${WS_PATH}`;
|
||||||
|
|
||||||
const options: WebSocket.ServerOptions = {
|
const options: WebSocket.ServerOptions = {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
server,
|
server,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socketServer = (
|
this.socketServer = config.createWebSocketServer
|
||||||
config.createWebSocketServer ?
|
? config.createWebSocketServer(options)
|
||||||
config.createWebSocketServer(options) :
|
: new Server(options);
|
||||||
new Server(options)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.socketServer.on("connection", (socket, req) => this._onSocketConnection(socket, req));
|
this.socketServer.on("connection", (socket, req) =>
|
||||||
|
this._onSocketConnection(socket, req),
|
||||||
|
);
|
||||||
this.socketServer.on("error", (error: Error) => this._onSocketError(error));
|
this.socketServer.on("error", (error: Error) => this._onSocketError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onSocketConnection(socket: WebSocket, req: IncomingMessage): void {
|
private _onSocketConnection(socket: WebSocket, req: IncomingMessage): void {
|
||||||
// An unhandled socket error might crash the server. Handle it first.
|
// An unhandled socket error might crash the server. Handle it first.
|
||||||
socket.on("error", error => this._onSocketError(error))
|
socket.on("error", (error) => this._onSocketError(error));
|
||||||
|
|
||||||
const { query = {} } = url.parse(req.url ?? '', true);
|
const { query = {} } = url.parse(req.url ?? "", true);
|
||||||
|
|
||||||
const { id, token, key }: IAuthParams = query;
|
const { id, token, key }: IAuthParams = query;
|
||||||
|
|
||||||
@ -79,10 +89,12 @@ export class WebSocketServer extends EventEmitter implements IWebSocketServer {
|
|||||||
if (client) {
|
if (client) {
|
||||||
if (token !== client.getToken()) {
|
if (token !== client.getToken()) {
|
||||||
// ID-taken, invalid token
|
// ID-taken, invalid token
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
type: MessageType.ID_TAKEN,
|
type: MessageType.ID_TAKEN,
|
||||||
payload: { msg: "ID is taken" }
|
payload: { msg: "ID is taken" },
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return socket.close();
|
return socket.close();
|
||||||
}
|
}
|
||||||
@ -98,8 +110,11 @@ export class WebSocketServer extends EventEmitter implements IWebSocketServer {
|
|||||||
this.emit("error", error);
|
this.emit("error", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _registerClient({ socket, id, token }:
|
private _registerClient({
|
||||||
{
|
socket,
|
||||||
|
id,
|
||||||
|
token,
|
||||||
|
}: {
|
||||||
socket: WebSocket;
|
socket: WebSocket;
|
||||||
id: string;
|
id: string;
|
||||||
token: string;
|
token: string;
|
||||||
@ -149,8 +164,8 @@ export class WebSocketServer extends EventEmitter implements IWebSocketServer {
|
|||||||
socket.send(
|
socket.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
payload: { msg }
|
payload: { msg },
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
socket.close();
|
socket.close();
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "@tsconfig/node16-strictest-esm/tsconfig.json",
|
"extends": "@tsconfig/node16-strictest-esm/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": [
|
"lib": ["esnext"],
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"exactOptionalPropertyTypes": false
|
"exactOptionalPropertyTypes": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["./src/**/*", "__test__/**/*"],
|
||||||
"./src/**/*",
|
"exclude": ["test", "bin"]
|
||||||
"__test__/**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"test",
|
|
||||||
"bin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user