Selaa lähdekoodia

feat: specify cors options via cli or js

Closes #196, #221
Jonas Gloning 2 vuotta sitten
vanhempi
commit
05f12cdc56
10 muutettua tiedostoa jossa 281 lisäystä ja 84 poistoa
  1. 14 14
      README.md
  2. 72 38
      __test__/peerjs.spec.ts
  3. 14 0
      __test__/utils.ts
  4. 17 6
      bin/peerjs.ts
  5. 8 4
      jest.config.js
  6. 147 18
      package-lock.json
  7. 1 0
      package.json
  8. 4 3
      src/api/index.ts
  9. 3 0
      src/config/index.ts
  10. 1 1
      src/instance.ts

+ 14 - 14
README.md

@@ -84,20 +84,20 @@ If you have your own server, you can attach PeerServer.
 ## Config / CLI options
 You can provide config object to `PeerServer` function or specify options for `peerjs` CLI.
 
-| CLI option | JS option | Description | Required | Default |
-| -------- | ------- | ------------- | :------: | :---------: |
-| `--port, -p` | `port` | Port to listen (number) | **Yes** | |
-| `--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 | `"/"` |
-| `--proxied` | `proxied` | Set `true` if PeerServer stays behind a reverse proxy (boolean) | No | `false` |
-| `--expire_timeout, -t` | `expire_timeout` | The amount of time after which a message sent will expire, the sender will then receive a `EXPIRE` message (milliseconds). | No | `5000` |
-| `--alive_timeout` | `alive_timeout` | Timeout for broken connection (milliseconds). If the server doesn't receive any data from client (includes `pong` messages), the client's connection will be destroyed. | No | `60000` |
-| `--concurrent_limit, -c` | `concurrent_limit` | Maximum number of clients' connections to WebSocket server (number) | No | `5000` |
-| `--sslkey` | `sslkey` | Path to SSL key (string) | No |  |
-| `--sslcert` | `sslcert` | Path to SSL certificate (string) | 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 |  |
-|  | `generateClientId` | A function which generate random client IDs when calling `/id` API method (`() => string`) | No | `uuid/v4` |
-
+| CLI option               | JS option          | Description                                                                                                                                                                                                                                           | Required |  Default   |
+|--------------------------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:----------:|
+| `--port, -p`             | `port`             | Port to listen (number)                                                                                                                                                                                                                               | **Yes**  |            |
+| `--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    |   `"/"`    |
+| `--proxied`              | `proxied`          | Set `true` if PeerServer stays behind a reverse proxy (boolean)                                                                                                                                                                                       |    No    |  `false`   |
+| `--expire_timeout, -t`   | `expire_timeout`   | The amount of time after which a message sent will expire, the sender will then receive a `EXPIRE` message (milliseconds).                                                                                                                            |    No    |   `5000`   |
+| `--alive_timeout`        | `alive_timeout`    | Timeout for broken connection (milliseconds). If the server doesn't receive any data from client (includes `pong` messages), the client's connection will be destroyed.                                                                               |    No    |  `60000`   |
+| `--concurrent_limit, -c` | `concurrent_limit` | Maximum number of clients' connections to WebSocket server (number)                                                                                                                                                                                   |    No    |   `5000`   |
+| `--sslkey`               | `sslkey`           | Path to SSL key (string)                                                                                                                                                                                                                              |    No    |            |
+| `--sslcert`              | `sslcert`          | Path to SSL certificate (string)                                                                                                                                                                                                                      |    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                                                                                                                                                                                                          |
+|                          | `generateClientId` | A function which generate random client IDs when calling `/id` API method (`() => string`)                                                                                                                                                            |    No    | `uuid/v4`  |
 ## Using HTTPS
 Simply pass in PEM-encoded certificate and key.
 

+ 72 - 38
__test__/peerjs.spec.ts

@@ -1,57 +1,91 @@
-import { describe, expect, it } from "@jest/globals";
+import {describe, expect, it} from "@jest/globals";
 
 import http from 'http';
 import expectedJson from '../app.json';
-import { spawn } from 'child_process';
-import path from 'path';
+import fetch from "node-fetch";
+import * as crypto from "crypto";
+import {startServer} from "./utils";
 
 const PORT = '9000';
 
 async function makeRequest() {
-  return new Promise<object>((resolve, reject) => {
-    http.get(`http://localhost:${PORT}/`, resp => {
-      let data = '';
+    return new Promise<object>((resolve, reject) => {
+        http.get(`http://localhost:${PORT}/`, resp => {
+            let data = '';
 
-      resp.on('data', chunk => {
-        data += chunk;
-      });
+            resp.on('data', chunk => {
+                data += chunk;
+            });
 
-      resp.on('end', () => {
-        resolve(JSON.parse(data));
-      });
+            resp.on('end', () => {
+                resolve(JSON.parse(data));
+            });
 
-    }).on("error", err => {
-      console.log("Error: " + err.message);
-      reject(err);
+        }).on("error", err => {
+            console.log("Error: " + err.message);
+            reject(err);
+        });
     });
-  });
 }
 
 describe('Check bin/peerjs', () => {
-	it('should return content of app.json file', async () => {
-		expect.assertions(1);
-    let resolver: () => void;
-    let rejecter: (err: unknown) => void;
-    const promise = new Promise<void>((resolve, reject) => {
-      resolver = resolve;
-      rejecter = reject;
+    it('should return content of app.json file', async () => {
+        expect.assertions(1);
+
+        const ls = await startServer()
+        try {
+            const resp = await makeRequest();
+            expect(resp).toEqual(expectedJson);
+        } finally {
+            ls.kill();
+        }
+
     });
 
-    const ls = spawn('node', [path.join(__dirname, '../', 'dist/bin/peerjs.js'), '--port', PORT]);
-      ls.stdout.on('data', async (data: string) => {
-      if (!data.includes('Started')) return;
-
-      try {
-        const resp = await makeRequest();
-        expect(resp).toEqual(expectedJson);
-        resolver();
-      } catch (error) {
-        rejecter(error);
-      } finally {
-        ls.kill('SIGKILL');
-      }
+    it('should reflect the origin header in CORS by default', async () => {
+        expect.assertions(1);
+
+        const ls = await startServer()
+        const origin = crypto.randomUUID();
+        try {
+            const res = await fetch(`http://localhost:${PORT}/peerjs/id`, {
+                headers: {
+                    Origin: origin
+                }
+            })
+            expect(res.headers.get("access-control-allow-origin")).toBe(origin)
+        } finally {
+            ls.kill()
+        }
     });
+    it('should respect the CORS parameters', async () => {
+        expect.assertions(3);
 
-    return promise;
-  });
+        const origin1 = crypto.randomUUID();
+        const origin2 = crypto.randomUUID();
+        const origin3 = crypto.randomUUID();
+        const ls = await startServer(["--cors", origin1, "--cors", origin2])
+        try {
+            const res1 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
+                headers: {
+                    Origin: origin1
+                }
+            })
+            expect(res1.headers.get("access-control-allow-origin")).toBe(origin1)
+            const res2 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
+                headers: {
+                    Origin: origin2
+                }
+            })
+            expect(res2.headers.get("access-control-allow-origin")).toBe(origin2)
+            const res3 = await fetch(`http://localhost:${PORT}/peerjs/id`, {
+                headers: {
+                    Origin: origin3
+                }
+            })
+            expect(res3.headers.get("access-control-allow-origin")).toBe(null)
+        } finally {
+            ls.kill()
+        }
+    });
 });

+ 14 - 0
__test__/utils.ts

@@ -1 +1,15 @@
+import {ChildProcessWithoutNullStreams, spawn} from "child_process";
+import path from "path";
+
 export const wait = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
+
+export const startServer = (params: string[] = []) => {
+    return new Promise<ChildProcessWithoutNullStreams>((resolve, reject)=> {
+        const ls = spawn('node', [path.join(__dirname, '../', 'dist/bin/peerjs.js'), '--port', "9000", ...params]);
+        ls.stdout.once("data", ()=> resolve(ls))
+        ls.stderr.once("data", ()=>{
+            ls.kill()
+            reject()
+        })
+    })
+}

+ 17 - 6
bin/peerjs.ts

@@ -8,6 +8,7 @@ import yargs from "yargs";
 import { hideBin } from 'yargs/helpers'
 import { PeerServer} from "../src";
 import type { AddressInfo } from "node:net";
+import type {CorsOptions} from "cors";
 
 const y = yargs(hideBin(process.argv));
 
@@ -73,18 +74,28 @@ const opts =  y
       demandOption: false,
       describe: "allow discovery of peers",
     },
-    proxied: {
-      type: "boolean",
-      demandOption: false,
-      describe: "Set true if PeerServer stays behind a reverse proxy",
-      default: false,
-    },
+      proxied: {
+          type: "boolean",
+          demandOption: false,
+          describe: "Set true if PeerServer stays behind a reverse proxy",
+          default: false,
+      },
+      cors: {
+          type: "string",
+          array: true,
+          describe: "Set the list of CORS origins",
+      },
   })
   .boolean("allow_discovery").parseSync();
 
 if(!opts.port){
     opts.port= parseInt(process.env["PORT"] as string)
 }
+if(opts.cors){
+    opts["corsOptions"] = {
+        origin: opts.cors
+    } satisfies CorsOptions;
+}
 process.on("uncaughtException", function (e) {
   console.error("Error: " + e);
 });

+ 8 - 4
jest.config.js

@@ -1,9 +1,13 @@
 /** @type {import('jest').Config} */
 const config = {
-  transform: {
-    "^.+\\.(t|j)sx?$": "@swc/jest",
-  },
-  collectCoverageFrom: ["./src/**"]
+    testEnvironment: "node",
+    transform: {
+        "^.+\\.(t|j)sx?$": "@swc/jest",
+    },
+    transformIgnorePatterns: [
+        // "node_modules"
+    ],
+    collectCoverageFrom: ["./src/**"]
 };
 
 export default config;

+ 147 - 18
package-lock.json

@@ -13,6 +13,7 @@
         "@types/ws": "^7.2.3 || ^8.0.0",
         "cors": "^2.8.5",
         "express": "^4.17.1",
+        "node-fetch": "^3.3.0",
         "ws": "^7.2.3 || ^8.0.0",
         "yargs": "^17.6.2"
       },
@@ -1799,6 +1800,26 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@octokit/request/node_modules/node-fetch": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
+      "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
+      "dev": true,
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@octokit/rest": {
       "version": "19.0.7",
       "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.7.tgz",
@@ -5028,6 +5049,14 @@
         "node": ">=8.0.0"
       }
     },
+    "node_modules/data-uri-to-buffer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/dateformat": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
@@ -5949,6 +5978,28 @@
         "bser": "2.1.1"
       }
     },
+    "node_modules/fetch-blob": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+      "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "paypal",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "dependencies": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      },
+      "engines": {
+        "node": "^12.20 || >= 14.13"
+      }
+    },
     "node_modules/figures": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
@@ -6093,6 +6144,17 @@
       "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
       "dev": true
     },
+    "node_modules/formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "dependencies": {
+        "fetch-blob": "^3.1.2"
+      },
+      "engines": {
+        "node": ">=12.20.0"
+      }
+    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -8758,6 +8820,24 @@
       "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
       "dev": true
     },
+    "node_modules/node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "github",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "engines": {
+        "node": ">=10.5.0"
+      }
+    },
     "node_modules/node-emoji": {
       "version": "1.11.0",
       "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
@@ -8768,23 +8848,20 @@
       }
     },
     "node_modules/node-fetch": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
-      "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
-      "dev": true,
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz",
+      "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==",
       "dependencies": {
-        "whatwg-url": "^5.0.0"
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
       },
       "engines": {
-        "node": "4.x || >=6.0.0"
-      },
-      "peerDependencies": {
-        "encoding": "^0.1.0"
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
       },
-      "peerDependenciesMeta": {
-        "encoding": {
-          "optional": true
-        }
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/node-fetch"
       }
     },
     "node_modules/node-gyp-build": {
@@ -13980,6 +14057,14 @@
       "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==",
       "dev": true
     },
+    "node_modules/web-streams-polyfill": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -15496,6 +15581,17 @@
         "is-plain-object": "^5.0.0",
         "node-fetch": "^2.6.7",
         "universal-user-agent": "^6.0.0"
+      },
+      "dependencies": {
+        "node-fetch": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
+          "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
+          "dev": true,
+          "requires": {
+            "whatwg-url": "^5.0.0"
+          }
+        }
       }
     },
     "@octokit/request-error": {
@@ -17778,6 +17874,11 @@
         "css-tree": "^1.1.2"
       }
     },
+    "data-uri-to-buffer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
+    },
     "dateformat": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
@@ -18460,6 +18561,15 @@
         "bser": "2.1.1"
       }
     },
+    "fetch-blob": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+      "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+      "requires": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      }
+    },
     "figures": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
@@ -18566,6 +18676,14 @@
       "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
       "dev": true
     },
+    "formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "requires": {
+        "fetch-blob": "^3.1.2"
+      }
+    },
     "forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -20527,6 +20645,11 @@
       "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
       "dev": true
     },
+    "node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
+    },
     "node-emoji": {
       "version": "1.11.0",
       "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
@@ -20537,12 +20660,13 @@
       }
     },
     "node-fetch": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
-      "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
-      "dev": true,
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz",
+      "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==",
       "requires": {
-        "whatwg-url": "^5.0.0"
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
       }
     },
     "node-gyp-build": {
@@ -24254,6 +24378,11 @@
       "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==",
       "dev": true
     },
+    "web-streams-polyfill": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
+    },
     "webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

+ 1 - 0
package.json

@@ -75,6 +75,7 @@
     "@types/ws": "^7.2.3 || ^8.0.0",
     "cors": "^2.8.5",
     "express": "^4.17.1",
+    "node-fetch": "^3.3.0",
     "ws": "^7.2.3 || ^8.0.0",
     "yargs": "^17.6.2"
   },

+ 4 - 3
src/api/index.ts

@@ -1,17 +1,18 @@
-import cors from "cors";
+import cors, {CorsOptions} from "cors";
 import express from "express";
 import publicContent from "../../app.json";
 import PublicApi from "./v1/public";
 import type {IConfig} from "../config";
 import type {IRealm} from "../models/realm";
 
-export const Api = ({ config, realm }: {
+export const Api = ({ config, realm, corsOptions }: {
   config: IConfig;
   realm: IRealm;
+  corsOptions: CorsOptions;
 }): express.Router => {
   const app = express.Router();
 
-  app.use(cors());
+  app.use(cors(corsOptions));
 
   app.get("/", (_, res) => {
     res.send(publicContent);

+ 3 - 0
src/config/index.ts

@@ -1,4 +1,5 @@
 import type {WebSocketServer, ServerOptions} from 'ws';
+import type {CorsOptions} from "cors";
 
 export interface IConfig {
   readonly host: string;
@@ -17,6 +18,7 @@ export interface IConfig {
   };
   readonly generateClientId?: () => string;
   readonly createWebSocketServer?: (options: ServerOptions) => WebSocketServer;
+  readonly corsOptions : CorsOptions;
 }
 
 const defaultConfig: IConfig = {
@@ -30,6 +32,7 @@ const defaultConfig: IConfig = {
   allow_discovery: false,
   proxied: false,
   cleanup_out_msgs: 1000,
+  corsOptions: {origin: true},
 };
 
 export default defaultConfig;

+ 1 - 1
src/instance.ts

@@ -31,7 +31,7 @@ export const createInstance = ({ app, server, options }: {
   const realm: IRealm = new Realm();
   const messageHandler = new MessageHandler(realm);
 
-  const api = Api({ config, realm });
+  const api = Api({ config, realm , corsOptions: options.corsOptions });
   const messagesExpire: IMessagesExpire = new MessagesExpire({ realm, config, messageHandler });
   const checkBrokenConnections = new CheckBrokenConnections({
     realm,