MateusAquino commited on
Commit
a571c16
·
1 Parent(s): 0717680

Add application files

Browse files
Files changed (11) hide show
  1. .dockerignore +1 -0
  2. .gitignore +1 -0
  3. .vscode/settings.json +3 -0
  4. Dockerfile +7 -0
  5. LICENSE +21 -0
  6. README.md +99 -12
  7. WatchParty.plugin.js +566 -0
  8. index.js +22 -0
  9. live.js +169 -0
  10. package-lock.json +1234 -0
  11. package.json +41 -0
.dockerignore ADDED
@@ -0,0 +1 @@
 
 
1
+ node_modules
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ node_modules
.vscode/settings.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "editor.tabSize": 2
3
+ }
Dockerfile ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM node
2
+ WORKDIR /app
3
+ ENV PORT=7860
4
+ COPY package.json /app
5
+ RUN npm install
6
+ COPY . /app
7
+ CMD ["node","index.js"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mateus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,99 @@
1
- ---
2
- title: WatchParty
3
- emoji: 🔥
4
- colorFrom: blue
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- short_description: BetterStremio WatchParty WS Server
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1 align="center">
2
+ <img width="250" src="./logo.png" align="center"></img>
3
+ </h1>
4
+
5
+ <p align="center">▶️ <strong>WatchParty</strong> is a BetterStremio session sharing plugin.</p>
6
+
7
+ <p align="center">
8
+ <a href="https://dramatic-hazel-epoch.glitch.me">
9
+ <img src="https://img.shields.io/website?url=https%3A%2F%2Fdramatic-hazel-epoch.glitch.me&label=Glitch"></img>
10
+ </a>
11
+ <a href="https://watch-party.adaptable.app">
12
+ <img src="https://img.shields.io/website?url=https%3A%2F%2Fwatch-party.adaptable.app&label=Adaptable"></img>
13
+ </a>
14
+ <a href="https://watchparty-kyiy.onrender.com">
15
+ <img src="https://img.shields.io/website?url=https%3A%2F%2Fwatchparty-kyiy.onrender.com&label=Render"></img>
16
+ </a>
17
+ </p>
18
+
19
+ ## 🎉 WatchParty
20
+
21
+ <p align="left">
22
+ <a target="_blank" href="https://github.com/MateusAquino/WatchParty/assets/16140783/bbe21561-c07a-48ae-9cf0-9613cbde665a">
23
+ <img width="300px" alt="Archer Logo" title="Archer Logo" align="right" src="https://github.com/MateusAquino/WatchParty/assets/16140783/5e986203-361b-4ef8-bd21-69bee97cdfcd"></img>
24
+ </a>
25
+ </p>
26
+
27
+ **WatchParty** is the first BetterStremio plugin ever made. It was developed to connect multiple Stremio sessions at once. You can use it to start a Stremio session with friends: create and join watch party, chat (soon) and share controls. No addon sharing required.
28
+
29
+ This Plugin works by intercepting events on the Stremio HTML5 Player and sharing to everyone in the party through a websocket connection. Currently there are three available servers to connect from: [Glitch](https://dramatic-hazel-epoch.glitch.me), [Adaptable](https://watch-party.adaptable.app) (temp disabled) and [Render](https://watchparty-kyiy.onrender.com).
30
+
31
+ This repository is currently accepting contributions and suggestions, feel free to do so :)
32
+
33
+ Note: `msg:` packet was not yet implemented in WatchParty's plugin.
34
+
35
+ ## 🌐 WatchParty Server
36
+
37
+ WatchParty is meant to be open for the public, but if you need to work on something locally you can also setup your own server.
38
+
39
+ ### Local server
40
+
41
+ You can Setup a local server by running:
42
+
43
+ ```bash
44
+ git clone https://github.com/MateusAquino/WatchParty
45
+ npm i
46
+ npm start
47
+ ```
48
+
49
+ A WatchParty server instance will be up at `localhost:3000`.
50
+ You may also change the env variables if needed: `PORT` and `SERVER_PREFIX` (single letter, defaults to `L`).
51
+
52
+ ### Server Connection
53
+
54
+ Connection protocol params must be separated by `#` and encoded (`encodeURIComponent`):
55
+
56
+ #### Create Party
57
+
58
+ - protocol: `c`
59
+ - `param[1]`: WatchParty protocol version
60
+ - `param[2]`: Username (string)
61
+ - `param[3]`: Party Password (string)
62
+ - `param[4]`: Party Name (string)
63
+ - `param[5]`: Join as Host? (1/0)
64
+
65
+ Usage example: `c#1#John#123#Example%20Party#1`
66
+
67
+ #### Join Party
68
+
69
+ - protocol: `j`
70
+ - `param[1]`: WatchParty protocol version
71
+ - `param[2]`: Username (string)
72
+ - `param[3]`: Party Code (string)
73
+ - `param[4]`: Party Password (string)
74
+
75
+ Usage example: `c#1#Jane#LJ7HLC#123`
76
+
77
+ ### Packets
78
+
79
+ #### Server Packets
80
+
81
+ ```elixir
82
+ upgrade -> Sent if protocol version mismatch
83
+ party:<party> -> Returns a JSON party whenever there is a host/clients update
84
+ msg:<id>:<msg> -> Returns the user id and the message broadcasted to the party
85
+ cmd:<latencies>:<cmd> -> Returns a JSON command used by WatchParty to sync actions and the sum of latencies from one client to another (example `cmd:130:go:["player", {...}]`)
86
+ badroom -> Sent if room code/password is wrong
87
+ ping -> Awaits for pong message (30s timeout)
88
+ ```
89
+
90
+ Note: party packet returns the generated code, the first letter should be unique for each server (or "L" for local)
91
+
92
+ #### Client Packets
93
+
94
+ ```elixir
95
+ msg:<msg> -> Broadcasts message to party
96
+ toggle:<userId> -> Toggles user host privileges (must be a host & > 1 host in the party)
97
+ cmd:<cmd> -> Broadcasts JSON command to party (must be a host)
98
+ pong -> Ping response
99
+ ```
WatchParty.plugin.js ADDED
@@ -0,0 +1,566 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = class WatchParty {
2
+ getName() {
3
+ return "WatchParty";
4
+ }
5
+ getImage() {
6
+ return "https://raw.githubusercontent.com/MateusAquino/WatchParty/main/logo.png";
7
+ }
8
+ getDescription() {
9
+ return "Start a Stremio session with friends: watch party, chat (soon) and share controls. No addon sharing required.";
10
+ }
11
+ getVersion() {
12
+ return "1.0.1";
13
+ }
14
+ getAuthor() {
15
+ return "MateusAquino";
16
+ }
17
+ getShareURL() {
18
+ return "https://github.com/MateusAquino/WatchParty";
19
+ }
20
+ getUpdateURL() {
21
+ return "https://raw.githubusercontent.com/MateusAquino/WatchParty/main/WatchParty.plugin.js";
22
+ }
23
+ onBoot() {}
24
+ onReady() {}
25
+ onLoad() {
26
+ const windowControls = document.getElementById("window-controls");
27
+ const WatchPartyControl = document.createElement("li");
28
+ WatchPartyControl.id = "wp-control";
29
+ WatchPartyControl.innerHTML = this.control();
30
+
31
+ const WatchPartyPopup = document.createElement("div");
32
+ WatchPartyPopup.id = "wp-popup";
33
+ WatchPartyPopup.innerHTML = this.popup();
34
+ WatchPartyPopup.classList.add("wp-noparty");
35
+ WatchPartyPopup.classList.add("wp-noparty");
36
+
37
+ WatchPartyControl.onclick = () => this.openUI();
38
+ windowControls.insertBefore(WatchPartyControl, windowControls.firstChild);
39
+
40
+ windowControls.insertBefore(
41
+ WatchPartyPopup,
42
+ windowControls.firstChild.nextSibling
43
+ );
44
+
45
+ window.WatchParty = {};
46
+ window.WatchParty.code = () => WatchParty.client?.party?.code;
47
+ window.WatchParty.create = () => this.btnCreate(this.element);
48
+ window.WatchParty.join = () => this.btnJoin(this.element);
49
+ window.WatchParty.confirm = () => this.btnConfirm(this.element);
50
+ window.WatchParty.toggle = (userId) =>
51
+ WatchParty.client.send(`toggle:${userId}`);
52
+ window.WatchParty.broadcast = this.broadcastCommand;
53
+ window.WatchParty.execute = this.execCommand;
54
+ window.WatchParty.inject = this.inject;
55
+ window.WatchParty.leave = () =>
56
+ WatchParty.client?.terminate?.() || WatchParty.client?.close?.();
57
+ window.WatchParty.failedServers = [];
58
+
59
+ const servers = this.getServers();
60
+ for (const server of Object.values(servers)) {
61
+ const statusEndpoint = server
62
+ .replace("ws://", "http://")
63
+ .replace("wss://", "https://");
64
+ const xhr = new XMLHttpRequest();
65
+ xhr.open("GET", statusEndpoint, true);
66
+ xhr.onload = () => {
67
+ if (xhr.status === 200)
68
+ console.log("[WatchParty] Server available: ", server);
69
+ };
70
+ xhr.send();
71
+ }
72
+
73
+ this.inject();
74
+ }
75
+ onEnable() {
76
+ this.onLoad();
77
+ }
78
+ onDisable() {
79
+ WatchParty.client?.terminate?.() || WatchParty.client?.close?.();
80
+ this.element("control").remove();
81
+ window.WatchParty = undefined;
82
+ }
83
+ getServers() {
84
+ return {
85
+ L: "ws://localhost:3000/",
86
+ R: "wss://watchparty-kyiy.onrender.com",
87
+ G: "wss://dramatic-hazel-epoch.glitch.me",
88
+ // A: "wss://watch-party.adaptable.app",
89
+ };
90
+ }
91
+ pickRandom(partyServer) {
92
+ const servers = this.getServers();
93
+ const availableServers = Object.keys(servers).filter(
94
+ (server) =>
95
+ server.startsWith(partyServer) &&
96
+ !window.WatchParty.failedServers.includes(servers[server])
97
+ );
98
+
99
+ if (availableServers.length > 0) {
100
+ const randomServer =
101
+ availableServers[Math.floor(Math.random() * availableServers.length)];
102
+ return servers[randomServer];
103
+ } else {
104
+ return false;
105
+ }
106
+ }
107
+ connect(protocol, partyServer = "") {
108
+ if (WatchParty.client)
109
+ WatchParty.client.terminate?.() || WatchParty.client.close?.();
110
+ const server = this.pickRandom(partyServer);
111
+ if (!server) {
112
+ document.getElementById("wp-popup").classList.remove("wp-loading");
113
+ document.getElementById("wp-popup").classList.add("wp-noparty");
114
+ return BetterStremio.Toasts.error(
115
+ "Failed to create/join party.",
116
+ "Couldn't find any servers available! Try again in a minute."
117
+ );
118
+ }
119
+ WatchParty.client = new WebSocket(server, protocol);
120
+ WatchParty.client.heartbeat = () => {
121
+ const client = this;
122
+ clearTimeout(this.pingTimeout);
123
+ this.pingTimeout = setTimeout(() => {
124
+ client.terminate?.() || client.close?.();
125
+ console.error("[WatchParty] Connection timeout!");
126
+ BetterStremio.Toasts.error(
127
+ "Disconnected from party!",
128
+ "You have been disconnected due to timeout."
129
+ );
130
+ }, 30000 + 4000);
131
+ };
132
+ const retry = () => this.connect(protocol, partyServer);
133
+ WatchParty.client.onerror = () => {
134
+ window.WatchParty.failedServers.push(server);
135
+ retry();
136
+
137
+ setTimeout(() => {
138
+ const index = window.WatchParty.failedServers.indexOf(server);
139
+ if (index > -1) window.WatchParty.failedServers.splice(index, 1);
140
+ }, 1 * 60 * 1000);
141
+ };
142
+ WatchParty.client.onmessage = this.handleMessage;
143
+ WatchParty.client.onopen = WatchParty.client.heartbeat;
144
+
145
+ window.WatchParty.warnOnClose = false;
146
+ const warnTimeout = setTimeout(() => {
147
+ window.WatchParty.warnOnClose = true;
148
+ }, 5000);
149
+
150
+ WatchParty.client.onclose = () => {
151
+ clearTimeout(warnTimeout);
152
+ if (window.WatchParty.warnOnClose)
153
+ BetterStremio.Toasts.warning("Disconnected!", "You've left the party.");
154
+ document.getElementById("wp-popup").classList.remove("wp-loading");
155
+ document.getElementById("wp-popup").classList.add("wp-noparty");
156
+ console.log("[WatchParty] Connection closed.");
157
+ clearTimeout(this.pingTimeout);
158
+ };
159
+ }
160
+ openUI() {
161
+ this.element("popup").classList.toggle("show");
162
+ }
163
+ element(id) {
164
+ return document.getElementById(`wp-${id}`);
165
+ }
166
+ btnCreate(element) {
167
+ delete this.isJoining;
168
+ element("create-btn").classList.add("selected");
169
+ element("join-btn").classList.remove("selected");
170
+ element("create").classList.remove("hidden");
171
+ element("join").classList.add("hidden");
172
+ }
173
+ btnJoin(element) {
174
+ this.isJoining = true;
175
+ element("create-btn").classList.remove("selected");
176
+ element("join-btn").classList.add("selected");
177
+ element("create").classList.add("hidden");
178
+ element("join").classList.remove("hidden");
179
+ }
180
+ btnConfirm(element) {
181
+ element("popup").classList.add("wp-loading");
182
+ if (!this.isJoining) {
183
+ const username = encodeURIComponent(element("create-user").value);
184
+ const partyName = encodeURIComponent(element("create-name").value);
185
+ const partyPass = encodeURIComponent(element("create-pass").value);
186
+ const joinAsHost = element("create-joinashost").checked;
187
+ const protocol = `c#1#${username}#${partyPass}#${partyName}#${
188
+ joinAsHost ? "1" : "0"
189
+ }`;
190
+ this.connect(protocol);
191
+ } else {
192
+ const username = encodeURIComponent(element("join-user").value);
193
+ const partyCode = encodeURIComponent(
194
+ element("join-code").value
195
+ ).toUpperCase();
196
+ const partyPass = encodeURIComponent(element("join-pass").value);
197
+ const protocol = `j#1#${username}#${partyCode}#${partyPass}`;
198
+ this.connect(protocol, partyCode.substring(0, 1));
199
+ }
200
+ }
201
+ handleMessage(message) {
202
+ document.getElementById("wp-popup").classList.remove("wp-loading");
203
+ const client = message.currentTarget;
204
+ if (message.data === "ping") {
205
+ client.send("pong");
206
+ client.heartbeat();
207
+ } else if (message.data === "badroom") {
208
+ document.getElementById("wp-join-pass").value = "";
209
+ BetterStremio.Toasts.error(
210
+ "Failed to join party!",
211
+ "The combination code/password does not exists."
212
+ );
213
+ } else if (message.data === "upgrade")
214
+ BetterStremio.Toasts.error(
215
+ "WatchParty version not supported!",
216
+ "Please upgrade your plugin version to use "
217
+ );
218
+ else if (message.data.startsWith("party:")) {
219
+ const newParty = JSON.parse(message.data.substring(6));
220
+ if (client.party && newParty) {
221
+ const oldMembers = client.party.members;
222
+ const newMembers = newParty.members;
223
+
224
+ if (oldMembers.length < newMembers.length) {
225
+ const joinedMember = newMembers.find(
226
+ (member) =>
227
+ !oldMembers.map((el) => el.userId).includes(member.userId)
228
+ );
229
+ BetterStremio.Toasts.info(
230
+ "Party Update",
231
+ `${joinedMember.userName} has joined the party`
232
+ );
233
+ } else if (oldMembers.length > newMembers.length) {
234
+ const leftMember = oldMembers.find(
235
+ (member) =>
236
+ !newMembers.map((el) => el.userId).includes(member.userId)
237
+ );
238
+ BetterStremio.Toasts.info(
239
+ "Party Update",
240
+ `${leftMember.userName} has left the party`
241
+ );
242
+ } else {
243
+ for (let i = 0; i < newMembers.length; i++) {
244
+ if (oldMembers[i].isHost !== newMembers[i].isHost) {
245
+ const member = newMembers[i];
246
+ const action = member.isHost ? "promoted to" : "demoted from";
247
+ BetterStremio.Toasts.info(
248
+ "Party Update",
249
+ `${member.userName} was ${action} host`
250
+ );
251
+ }
252
+ }
253
+ }
254
+ }
255
+ client.party = newParty;
256
+ document.getElementById("wp-popup").classList.remove("wp-noparty");
257
+ document.getElementById("wp-partyname").innerText = newParty.name;
258
+ document.getElementById("wp-partycode").innerText = newParty.code;
259
+ document.getElementById("wp-partymembers").innerHTML = newParty.members
260
+ .map(
261
+ (member) =>
262
+ `<div class="row user-row">
263
+ ${member.userName} ${member.isHost ? "(host)" : ""}
264
+ <button onclick="WatchParty.toggle('${
265
+ member.userId
266
+ }')">Toggle Host</button>
267
+ </div>`
268
+ )
269
+ .join("");
270
+ } else if (message.data.startsWith("cmd:")) {
271
+ const cmdLine = message.data.substring(4);
272
+ const separatorIndex = cmdLine.indexOf(":");
273
+ if (separatorIndex === -1) return;
274
+ const latency = cmdLine.substring(0, separatorIndex);
275
+ const remainingData = cmdLine.substring(separatorIndex + 1);
276
+ const secondSeparatorIndex = remainingData.indexOf(":");
277
+ if (secondSeparatorIndex === -1) return;
278
+ const cmd = remainingData.substring(0, secondSeparatorIndex);
279
+ const jsonData = remainingData.substring(secondSeparatorIndex + 1);
280
+ if (!jsonData) return;
281
+ const data = JSON.parse(jsonData);
282
+ if (data) window.WatchParty.execute(latency, cmd, data);
283
+ }
284
+ }
285
+ inject() {
286
+ if (!window.WatchParty.injectedStateChange) {
287
+ BetterStremio.StremioRoot.$on("$stateChangeSuccess", (_scs) => {
288
+ window.WatchParty.inject();
289
+ });
290
+ window.WatchParty.injectedStateChange = true;
291
+ }
292
+
293
+ if (!window.WatchParty.injectedGo) {
294
+ const oldGoFn = BetterStremio.Modules.$state.go;
295
+ BetterStremio.Modules.$state.go = function () {
296
+ if (arguments[0] === "player/NO_SPREAD") arguments[0] = "player";
297
+ else if (arguments[0] === "player")
298
+ window.WatchParty.broadcast("go", Array.from(arguments));
299
+
300
+ return oldGoFn.apply(oldGoFn, arguments);
301
+ };
302
+ window.WatchParty.injectedGo = true;
303
+ }
304
+
305
+ if (!window.WatchParty.injectedHtml5 && BetterStremio.Modules.deviceHtml5) {
306
+ BetterStremio.Modules.deviceHtml5.addListener("statechanged", (state) => {
307
+ if (state.NO_SPREAD) return;
308
+ if (
309
+ BetterStremio.Modules.deviceHtml5.time ===
310
+ window.WatchParty.NO_SPREAD_TIME
311
+ )
312
+ return;
313
+ window.WatchParty.broadcast("state", {
314
+ state,
315
+ time: BetterStremio.Modules.deviceHtml5.time,
316
+ paused: BetterStremio.Modules.deviceHtml5.paused,
317
+ playbackSpeed: BetterStremio.Modules.deviceHtml5.playbackSpeed,
318
+ });
319
+ });
320
+ BetterStremio.Modules.deviceHtml5.addListener("timeupdate", () => {
321
+ delete window.WatchParty.NO_SPREAD;
322
+ });
323
+ BetterStremio.Modules.deviceHtml5.addListener("error", (error) => {
324
+ window.WatchParty.broadcast("error", error);
325
+ });
326
+ window.WatchParty.injectedHtml5 = true;
327
+ }
328
+
329
+ if (!window.WatchParty.injectedMPV && BetterStremio.Modules.deviceMPV) {
330
+ BetterStremio.Modules.deviceMPV.addListener("statechanged", (state) => {
331
+ if (state.NO_SPREAD) return;
332
+ if (
333
+ BetterStremio.Modules.deviceMPV.time ===
334
+ window.WatchParty.NO_SPREAD_TIME
335
+ )
336
+ return;
337
+ window.WatchParty.broadcast("state", {
338
+ state,
339
+ time: BetterStremio.Modules.deviceMPV.time,
340
+ paused: BetterStremio.Modules.deviceMPV.paused,
341
+ playbackSpeed: BetterStremio.Modules.deviceMPV.playbackSpeed,
342
+ });
343
+ });
344
+ BetterStremio.Modules.deviceMPV.addListener("timeupdate", () => {
345
+ delete window.WatchParty.NO_SPREAD;
346
+ });
347
+ BetterStremio.Modules.deviceMPV.addListener("error", (error) => {
348
+ window.WatchParty.broadcast("error", error);
349
+ });
350
+ window.WatchParty.injectedMPV = true;
351
+ }
352
+ }
353
+
354
+ broadcastCommand(cmd, data) {
355
+ if (WatchParty.client && !window.WatchParty.NO_SPREAD) {
356
+ try {
357
+ const dataStr = JSON.stringify(data);
358
+ WatchParty.client.send?.(`cmd:${cmd}:${dataStr}`);
359
+ } catch (e) {}
360
+ }
361
+ }
362
+ execCommand(latency, cmd, data) {
363
+ const strData = JSON.stringify(data);
364
+ if (cmd === "go") {
365
+ const playerData = data;
366
+ playerData.shift();
367
+ BetterStremio.Modules.$state.go("player/NO_SPREAD", ...playerData);
368
+ } else if (
369
+ cmd === "state" &&
370
+ (BetterStremio.Modules.deviceMPV || BetterStremio.Modules.deviceHtml5) &&
371
+ !window.WatchParty.NO_SPREAD
372
+ ) {
373
+ window.WatchParty.NO_SPREAD = true;
374
+ if (data.playbackSpeed)
375
+ BetterStremio.Modules.deviceHtml5.playbackSpeed = data.playbackSpeed;
376
+ if (data.playbackSpeed)
377
+ BetterStremio.Modules.deviceMPV.playbackSpeed = data.playbackSpeed;
378
+ if (BetterStremio.Modules.deviceHtml5.paused !== data.paused)
379
+ BetterStremio.Modules.deviceHtml5.paused = data.paused;
380
+ if (BetterStremio.Modules.deviceMPV.paused !== data.paused)
381
+ BetterStremio.Modules.deviceMPV.paused = data.paused;
382
+ if (window.WatchParty.LAST_STATE !== strData && !data.paused) {
383
+ window.WatchParty.NO_SPREAD_TIME = data.time + parseInt(latency);
384
+ BetterStremio.Modules.deviceHtml5.time = data.time + parseInt(latency);
385
+ BetterStremio.Modules.deviceMPV.time = data.time + parseInt(latency);
386
+ }
387
+ BetterStremio.Modules.deviceHtml5?.emit?.("statechanged", {
388
+ ...data.state,
389
+ NO_SPREAD: true,
390
+ });
391
+ BetterStremio.Modules.deviceMPV?.emit?.("statechanged", {
392
+ ...data.state,
393
+ NO_SPREAD: true,
394
+ });
395
+ window.WatchParty.LAST_STATE = strData;
396
+ delete window.WatchParty.NO_SPREAD;
397
+ }
398
+ }
399
+ control() {
400
+ return `<svg class="icon" viewBox="0,0,256,256"><path d="M 106.56 10.56 C 94.613 10.56 84.171 17.067 78.731 26.88 L 78.4 26.453 C 77.12 26.24 75.744 26.251 74.357 26.251 C 58.997 26.251 46.176 37.013 43.083 51.52 C 35.893 55.467 30.443 62.123 27.957 70.101 C 26.4 70.741 27.093 70.336 24.683 71.307 C 23.029 72.395 22.912 72.491 22.155 73.824 C 21.227 75.072 20.363 77.76 20.789 80.853 L 42.123 235.52 C 42.869 240.853 47.456 244.789 52.789 244.789 L 141.941 244.789 C 142.528 244.896 143.072 245.12 143.691 245.12 L 207.691 245.12 C 213.237 245.12 217.824 240.853 218.357 235.413 L 228.171 122.667 L 234.667 122.667 L 234.667 101.333 L 219.083 101.333 C 218.901 101.323 218.741 101.227 218.56 101.227 L 211.872 101.227 L 215.456 90.56 L 230.837 90.56 L 230.837 69.227 L 207.893 69.227 C 203.307 69.227 199.083 71.467 197.696 75.84 L 197.547 76.267 L 189.248 101.227 L 179.712 101.227 L 181.12 80.416 C 181.333 77.536 180.277 74.677 178.357 72.544 C 177.205 71.328 175.755 70.528 174.208 69.984 C 171.755 62.336 166.571 55.947 159.787 52.064 C 159.253 35.744 146.453 22.507 130.453 21.227 C 124.587 14.72 116.053 10.56 106.56 10.56 Z M 105.813 31.349 C 109.867 31.349 113.291 33.483 115.211 36.789 C 117.344 40.523 121.504 42.656 125.877 42.123 L 127.349 42.123 C 133.216 42.123 138.016 46.816 138.016 52.789 C 138.016 53.856 137.92 54.827 137.707 55.787 C 136.853 58.56 137.28 61.653 138.88 64.213 C 140.16 66.453 142.293 68.064 144.853 68.917 L 56.651 68.917 C 60.917 67.424 63.797 63.253 63.691 58.667 L 63.691 58.357 C 63.691 52.384 68.384 47.691 74.357 47.691 C 76.384 47.691 78.187 48.203 79.787 49.163 C 82.88 50.976 86.709 51.2 90.016 49.6 C 93.216 48 95.467 44.789 95.893 41.269 C 96.533 35.936 99.989 31.349 105.536 31.349 L 105.813 31.349 Z M 43.872 90.357 L 54.56 90.357 L 72.917 223.456 L 62.187 223.456 L 43.872 90.357 Z M 76.107 90.357 L 158.912 90.357 L 158.229 101.227 L 133.227 101.227 C 133.067 101.227 132.917 101.323 132.747 101.333 L 117.333 101.333 L 117.333 122.667 L 123.232 122.667 L 132 223.456 L 94.453 223.456 L 76.107 90.357 Z M 144.853 122.667 L 206.731 122.667 L 197.984 223.893 L 159.456 223.893 L 159.456 223.456 L 153.557 223.456 L 144.853 122.667 Z" style="paint-order: fill; fill: currentColor; stroke: rgb(0, 0, 0); mix-blend-mode: exclusion; stroke-width: 6px;" fill-rule="nonzero" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none"></path></svg>`;
401
+ }
402
+ popup() {
403
+ return `
404
+ <h3>WatchParty <span>v${this.getVersion()}</span></h3>
405
+
406
+ <button id="wp-create-btn" class="wp-noparty-show selected" onclick="WatchParty.create()">Create</button>
407
+ <button id="wp-join-btn" class="wp-noparty-show" onclick="WatchParty.join()">Join</button>
408
+
409
+ <div id="wp-create" class="tab">
410
+ <div class="row">Username: <input id="wp-create-user" oninput="BetterStremio.Data.store('WatchParty', 'user', this.value)" value="${
411
+ BetterStremio.Data.read("WatchParty", "user") || ""
412
+ }" autocomplete="false" type="text"/></div>
413
+ <div class="row">Party Name: <input id="wp-create-name" oninput="BetterStremio.Data.store('WatchParty', 'party', this.value)" value="${
414
+ BetterStremio.Data.read("WatchParty", "party") || "Watch Party"
415
+ }" autocomplete="false" type="text"/></div>
416
+ <div class="row">Party Pass: <input id="wp-create-pass" oninput="BetterStremio.Data.store('WatchParty', 'pass', this.value)" value="${
417
+ BetterStremio.Data.read("WatchParty", "pass") || ""
418
+ }" autocomplete="false" type="text"/></div>
419
+ <div class="row">New members as host: <input id="wp-create-joinashost" onchange="BetterStremio.Data.store('WatchParty', 'joinAsHost', this.checked)" ${
420
+ BetterStremio.Data.read("WatchParty", "joinAsHost") === "true"
421
+ ? "checked"
422
+ : ""
423
+ } autocomplete="false" type="checkbox"/></div>
424
+ </div>
425
+
426
+ <div id="wp-join" class="tab hidden">
427
+ <div class="row">Username: <input id="wp-join-user" oninput="BetterStremio.Data.store('WatchParty', 'user', this.value)" value="${
428
+ BetterStremio.Data.read("WatchParty", "user") || ""
429
+ }" autocomplete="false" type="text"/></div>
430
+ <div class="row">Party Code: <input id="wp-join-code" autocomplete="false" type="text"/></div>
431
+ <div class="row">Party Pass: <input id="wp-join-pass" oninput="BetterStremio.Data.store('WatchParty', 'pass', this.value)" value="${
432
+ BetterStremio.Data.read("WatchParty", "pass") || ""
433
+ }" autocomplete="false" type="text"/></div>
434
+ </div>
435
+
436
+ <div id="wp-loading" class="tab">
437
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="208" height="208" style="shape-rendering: auto;display: block;"><g data-idx="1"><circle stroke-linecap="round" fill="none" stroke="#9370db" stroke-width="3" r="18" cy="50" cx="50" data-idx="2" stroke-dasharray="28.274333882308138 28.274333882308138"></circle><g data-idx="4"></g></g></svg>
438
+ </div>
439
+
440
+ <div id="wp-inparty" class="tab" style="margin-top: 10px;">
441
+ <h3 class="row" style="justify-content: flex-start; margin-bottom: 10px;">
442
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0,0,256,256"><g fill="currentColor" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10"><g transform="scale(10.66667,10.66667)"><path d="M12,2.09961l-11,9.90039h3v9h1h15v-9h3zM12,4.79102l6,5.40039v8.80859h-2v-6h-3v6h-7v-8.80859zM8,13v3h3v-3z"></path></g></g></svg>
443
+ <span id="wp-partyname" style="color: inherit; font-size: 1em;"></span>
444
+ <button style="margin: 0; margin-left: auto; margin-right: 8px;" onclick="WatchParty.leave()">Leave</button>
445
+ </h3>
446
+ <h3 class="row" onclick="BetterStremio.Sharing.copyToClipboard(WatchParty.code())" style="cursor: pointer; color: gray;justify-content: flex-start;margin-bottom: 10px;">
447
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0,0,256,256" style=" margin-left: 3px; margin-right: 3px; "><g fill="currentColor" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10"><g transform="scale(10.66667,10.66667)"><path d="M12,1c-1.64501,0 -3,1.35499 -3,3c0,0.35185 0.07394,0.68511 0.1875,1h-6.1875c-1.103,0 -2,0.897 -2,2v12c0,1.103 0.897,2 2,2h18c1.103,0 2,-0.897 2,-2v-12c0,-1.103 -0.897,-2 -2,-2h-6.1875c0.11356,-0.31489 0.1875,-0.64815 0.1875,-1c0,-1.64501 -1.35499,-3 -3,-3zM12,3c0.56413,0 1,0.43587 1,1c0,0.56413 -0.43587,1 -1,1c-0.56413,0 -1,-0.43587 -1,-1c0,-0.56413 0.43587,-1 1,-1zM3,7h9h9l0.00195,12h-18.00195zM9,9c-1.10457,0 -2,0.89543 -2,2c0,1.10457 0.89543,2 2,2c1.10457,0 2,-0.89543 2,-2c0,-1.10457 -0.89543,-2 -2,-2zM15,10v2h4v-2zM9,14c-2.185,0 -4,0.9088 -4,2.2168v0.7832h8v-0.7832c0,-1.308 -1.815,-2.2168 -4,-2.2168zM15,14v2h4v-2z"></path></g></g></svg>
448
+ <span id="wp-partycode" style="color: inherit;font-size: .8em;"></span>
449
+ </h3>
450
+ <div id="wp-partymembers"></div>
451
+ </div>
452
+
453
+ <button class="wp-noparty-show" onclick="WatchParty.confirm()" style=" float: right; margin-top: 15px;">Enter</button>
454
+
455
+ <style type="text/css">
456
+ #wp-popup .tab.hidden {
457
+ display: none;
458
+ }
459
+
460
+ #wp-popup {
461
+ display: none;
462
+ background: #0c0c11;
463
+ min-width: 300px;
464
+ width: fit-content;
465
+ position: absolute;
466
+ top: 50px;
467
+ right: 95px;
468
+ border-radius: 8px;
469
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 10px 15px;
470
+ padding: 20px;
471
+ }
472
+
473
+ #wp-popup.show {
474
+ display: block;
475
+ }
476
+
477
+ #wp-popup h3 span {
478
+ color: rgba(255, 255, 255, 0.5);
479
+ font-size: 0.7em;
480
+ }
481
+
482
+ #wp-popup button {
483
+ padding: 4px 10px;
484
+ margin: 8px 8px 8px 0px;
485
+ border-radius: 4px;
486
+ background-color: mediumpurple;
487
+ color: white;
488
+ cursor: pointer;
489
+ font-weight: bold;
490
+ }
491
+
492
+ #wp-popup button:hover,
493
+ #wp-popup button.selected {
494
+ background-color: rebeccapurple;
495
+ }
496
+
497
+ #wp-popup .row {
498
+ display: flex;
499
+ align-items: center;
500
+ margin-top: 8px;
501
+ white-space: nowrap;
502
+ gap: 10px;
503
+ justify-content: space-between;
504
+ }
505
+
506
+ #wp-popup .row.user-row {
507
+ padding-top: 5px;
508
+ margin-top: 5px;
509
+ border-top: 1px solid #ffffff22;
510
+ }
511
+
512
+ #wp-popup .row div {
513
+ display: none !important;
514
+ }
515
+
516
+ #wp-popup input[type="text"] {
517
+ width: 100%;
518
+ min-width: 30px;
519
+ }
520
+
521
+ .wp-loading div, #wp-loading {
522
+ display: none;
523
+ }
524
+
525
+ .wp-loading #wp-loading {
526
+ display: flex;
527
+ justify-content: center;
528
+ }
529
+
530
+ .wp-loading button {
531
+ display: none !important;
532
+ }
533
+
534
+ .wp-noparty button,
535
+ .wp-noparty-show,
536
+ #wp-popup:not(.wp-noparty) .tab,
537
+ #wp-inparty {
538
+ display: none;
539
+ }
540
+
541
+ #wp-popup:not(.wp-noparty) #wp-inparty {
542
+ display: block;
543
+ }
544
+
545
+ .wp-noparty button.wp-noparty-show {
546
+ display: inline;
547
+ }
548
+
549
+ #wp-popup #wp-loading svg {
550
+ animation-name: spin;
551
+ animation-duration: 650ms;
552
+ animation-iteration-count: infinite;
553
+ animation-timing-function: linear;
554
+ }
555
+
556
+ @keyframes spin {from {transform:rotate(0deg);}to {transform:rotate(360deg);}}
557
+
558
+ @media (min-width: 1500px) {
559
+ #wp-popup {
560
+ top: 60px;
561
+ right: 118px;
562
+ }
563
+ }
564
+ </style>`;
565
+ }
566
+ };
index.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require("express");
2
+ const cors = require("cors");
3
+ const helmet = require("helmet");
4
+ const morgan = require("morgan");
5
+ const watchTogetherLive = require("./live");
6
+
7
+ const app = express();
8
+
9
+ app.use(cors({ origin: "*" }));
10
+ app.use(helmet());
11
+ app.use(express.json());
12
+ app.use(morgan("dev"));
13
+
14
+ app.get("/", (_req, res, _next) => {
15
+ res.json({ status: "ok" });
16
+ });
17
+
18
+ const server = app.listen(process.env.PORT || 3000, () => {
19
+ console.log(`App Express is running!`);
20
+ });
21
+
22
+ watchTogetherLive(server);
live.js ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const WebSocket = require("ws");
2
+
3
+ const watchPartyVersion = "1";
4
+ const serverPrefix = process.env.SERVER_PREFIX || "L";
5
+
6
+ let wss;
7
+ module.exports = (server) => {
8
+ wss = new WebSocket.Server({ server });
9
+ wss.on("connection", onConnection);
10
+
11
+ const interval = setInterval(
12
+ () =>
13
+ wss.clients.forEach((ws) => {
14
+ if (ws.isAlive === false) return ws.terminate();
15
+
16
+ ws.isAlive = false;
17
+ ws.startMsg = Date.now();
18
+ ws.send("ping");
19
+ }),
20
+ 30000
21
+ );
22
+
23
+ wss.on("close", () => clearInterval(interval));
24
+ console.log(`App Web Socket Server is running!`);
25
+ return wss;
26
+ };
27
+
28
+ function onConnection(ws, req) {
29
+ if (!req.headers["sec-websocket-protocol"]) return ws.terminate();
30
+ const protocol = req.headers["sec-websocket-protocol"].charAt(0);
31
+ ws.userId = req.headers["sec-websocket-key"];
32
+
33
+ if (!protocols[protocol]) return ws.terminate();
34
+ const params = req.headers["sec-websocket-protocol"]
35
+ .split("#")
36
+ .map((p) => decodeURIComponent(p));
37
+ const v = params[1];
38
+ if (v !== watchPartyVersion) {
39
+ ws.send("upgrade");
40
+ return ws.terminate();
41
+ }
42
+ ws.isAlive = true;
43
+ ws.startMsg = Date.now();
44
+ ws.send("ping");
45
+ ws.on("message", (data, binary) => (binary ? null : onMessage(ws, data)));
46
+ ws.on("error", (error) => onError(ws, error));
47
+ ws.on("close", () => onClose(ws));
48
+ protocols[protocol](ws, params);
49
+ }
50
+
51
+ function onError(ws, err) {
52
+ console.error(`onError: ${err.message}`);
53
+ }
54
+
55
+ function onClose(ws) {
56
+ const partyCode = ws.partyCode;
57
+ if (!partyCode) return;
58
+ const party = parties[partyCode];
59
+ if (party.clients.length === 1) delete parties[partyCode];
60
+ else {
61
+ party.clients = party.clients.filter((el) => el !== ws);
62
+ if (!party.clients.find((ws) => ws.isHost)) party.clients[0].isHost = true;
63
+ updateParty(party);
64
+ }
65
+ }
66
+
67
+ function updateParty(party) {
68
+ const partyMembers = party.clients.map((ws) => ({
69
+ userId: ws.userId,
70
+ userName: ws.userName,
71
+ isHost: ws.isHost,
72
+ }));
73
+
74
+ party.clients.forEach((ws) => {
75
+ ws.send(
76
+ "party:" +
77
+ JSON.stringify({
78
+ name: party.name,
79
+ code: party.code,
80
+ members: partyMembers,
81
+ })
82
+ );
83
+ });
84
+ }
85
+
86
+ function onMessage(ws, data) {
87
+ data = data.toString();
88
+ if (data.startsWith("msg:")) {
89
+ parties[ws.partyCode].clients.forEach((client) => {
90
+ if (client.readyState === WebSocket.OPEN) {
91
+ client.send("msg:" + ws.userId + ":" + data.substring(4));
92
+ }
93
+ });
94
+ } else if (ws.isHost && data.startsWith("toggle:")) {
95
+ const id = data.substring(7);
96
+ const hosts = parties[ws.partyCode].clients.filter((ws) => ws.isHost);
97
+ const user = parties[ws.partyCode].clients.find(
98
+ (client) => client.userId === id
99
+ );
100
+ if (user && (hosts.length > 1 || !user.isHost)) {
101
+ user.isHost = !user.isHost;
102
+ updateParty(parties[ws.partyCode]);
103
+ }
104
+ } else if (ws.isHost && data.startsWith("cmd:")) {
105
+ parties[ws.partyCode].clients.forEach((client) => {
106
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
107
+ const latencies = (ws.latency || 0) + (client.latency || 0);
108
+ client.send("cmd:" + Math.round(latencies) + ":" + data.substring(4));
109
+ }
110
+ });
111
+ } else if (data === "pong") {
112
+ ws.isAlive = true;
113
+ ws.latency = ws.latency = Date.now() - ws.startMsg;
114
+ }
115
+ }
116
+
117
+ const protocols = {
118
+ c: createParty,
119
+ j: joinParty,
120
+ };
121
+
122
+ const parties = {};
123
+
124
+ function generatePartyCode(tries = 0) {
125
+ const characters = "0123456789ABCDEHIJKLMNORSTUVWYZ";
126
+ const length = 5 + Math.floor(tries / 3);
127
+ let code = serverPrefix;
128
+ for (let i = 0; i < length; i++) {
129
+ code += characters.charAt(Math.floor(Math.random() * characters.length));
130
+ }
131
+ if (parties[code]) return generatePartyCode(tries + 1);
132
+ return code;
133
+ }
134
+
135
+ function createParty(ws, params) {
136
+ const userName = params[2] || "Anonymous";
137
+ const password = params[3] || "";
138
+ const name = params[4] || "WatchParty";
139
+ const joinAsHost = params[5] || "0";
140
+ const code = generatePartyCode();
141
+ ws.partyCode = code;
142
+ ws.isHost = true;
143
+ ws.userName = userName;
144
+ parties[code] = {
145
+ code,
146
+ password,
147
+ name,
148
+ joinAsHost: joinAsHost === "1",
149
+ clients: [ws],
150
+ };
151
+ updateParty(parties[code]);
152
+ }
153
+
154
+ function joinParty(ws, params) {
155
+ const userName = params[2] || "Anonymous";
156
+ const code = params[3] || "???";
157
+ const password = params[4] || "";
158
+ const party = parties[code];
159
+ if (party && party.password === password) {
160
+ ws.partyCode = code;
161
+ ws.isHost = party.joinAsHost;
162
+ ws.userName = userName;
163
+ party.clients.push(ws);
164
+ updateParty(party);
165
+ } else {
166
+ ws.send("badroom");
167
+ ws.terminate();
168
+ }
169
+ }
package-lock.json ADDED
@@ -0,0 +1,1234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "watchparty",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "watchparty",
9
+ "version": "1.0.0",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "bufferutil": "*",
13
+ "cors": "^2.8.5",
14
+ "dotenv": "^16.4.5",
15
+ "express": "^4.19.2",
16
+ "helmet": "^7.1.0",
17
+ "morgan": "^1.10.0",
18
+ "ws": "^8.17.0"
19
+ },
20
+ "devDependencies": {
21
+ "nodemon": "^3.1.3"
22
+ },
23
+ "optionalDependencies": {
24
+ "bufferutil": "^4.0.8"
25
+ }
26
+ },
27
+ "node_modules/accepts": {
28
+ "version": "1.3.8",
29
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
30
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
31
+ "dependencies": {
32
+ "mime-types": "~2.1.34",
33
+ "negotiator": "0.6.3"
34
+ },
35
+ "engines": {
36
+ "node": ">= 0.6"
37
+ }
38
+ },
39
+ "node_modules/anymatch": {
40
+ "version": "3.1.3",
41
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
42
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
43
+ "dev": true,
44
+ "dependencies": {
45
+ "normalize-path": "^3.0.0",
46
+ "picomatch": "^2.0.4"
47
+ },
48
+ "engines": {
49
+ "node": ">= 8"
50
+ }
51
+ },
52
+ "node_modules/array-flatten": {
53
+ "version": "1.1.1",
54
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
55
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
56
+ },
57
+ "node_modules/balanced-match": {
58
+ "version": "1.0.2",
59
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
60
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
61
+ "dev": true
62
+ },
63
+ "node_modules/basic-auth": {
64
+ "version": "2.0.1",
65
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
66
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
67
+ "dependencies": {
68
+ "safe-buffer": "5.1.2"
69
+ },
70
+ "engines": {
71
+ "node": ">= 0.8"
72
+ }
73
+ },
74
+ "node_modules/basic-auth/node_modules/safe-buffer": {
75
+ "version": "5.1.2",
76
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
77
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
78
+ },
79
+ "node_modules/binary-extensions": {
80
+ "version": "2.3.0",
81
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
82
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
83
+ "dev": true,
84
+ "engines": {
85
+ "node": ">=8"
86
+ },
87
+ "funding": {
88
+ "url": "https://github.com/sponsors/sindresorhus"
89
+ }
90
+ },
91
+ "node_modules/body-parser": {
92
+ "version": "1.20.2",
93
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
94
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
95
+ "dependencies": {
96
+ "bytes": "3.1.2",
97
+ "content-type": "~1.0.5",
98
+ "debug": "2.6.9",
99
+ "depd": "2.0.0",
100
+ "destroy": "1.2.0",
101
+ "http-errors": "2.0.0",
102
+ "iconv-lite": "0.4.24",
103
+ "on-finished": "2.4.1",
104
+ "qs": "6.11.0",
105
+ "raw-body": "2.5.2",
106
+ "type-is": "~1.6.18",
107
+ "unpipe": "1.0.0"
108
+ },
109
+ "engines": {
110
+ "node": ">= 0.8",
111
+ "npm": "1.2.8000 || >= 1.4.16"
112
+ }
113
+ },
114
+ "node_modules/body-parser/node_modules/debug": {
115
+ "version": "2.6.9",
116
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
117
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
118
+ "dependencies": {
119
+ "ms": "2.0.0"
120
+ }
121
+ },
122
+ "node_modules/body-parser/node_modules/ms": {
123
+ "version": "2.0.0",
124
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
125
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
126
+ },
127
+ "node_modules/brace-expansion": {
128
+ "version": "1.1.11",
129
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
130
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
131
+ "dev": true,
132
+ "dependencies": {
133
+ "balanced-match": "^1.0.0",
134
+ "concat-map": "0.0.1"
135
+ }
136
+ },
137
+ "node_modules/braces": {
138
+ "version": "3.0.3",
139
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
140
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
141
+ "dev": true,
142
+ "dependencies": {
143
+ "fill-range": "^7.1.1"
144
+ },
145
+ "engines": {
146
+ "node": ">=8"
147
+ }
148
+ },
149
+ "node_modules/bufferutil": {
150
+ "version": "4.0.8",
151
+ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
152
+ "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
153
+ "hasInstallScript": true,
154
+ "optional": true,
155
+ "dependencies": {
156
+ "node-gyp-build": "^4.3.0"
157
+ },
158
+ "engines": {
159
+ "node": ">=6.14.2"
160
+ }
161
+ },
162
+ "node_modules/bytes": {
163
+ "version": "3.1.2",
164
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
165
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
166
+ "engines": {
167
+ "node": ">= 0.8"
168
+ }
169
+ },
170
+ "node_modules/call-bind": {
171
+ "version": "1.0.7",
172
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
173
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
174
+ "dependencies": {
175
+ "es-define-property": "^1.0.0",
176
+ "es-errors": "^1.3.0",
177
+ "function-bind": "^1.1.2",
178
+ "get-intrinsic": "^1.2.4",
179
+ "set-function-length": "^1.2.1"
180
+ },
181
+ "engines": {
182
+ "node": ">= 0.4"
183
+ },
184
+ "funding": {
185
+ "url": "https://github.com/sponsors/ljharb"
186
+ }
187
+ },
188
+ "node_modules/chokidar": {
189
+ "version": "3.6.0",
190
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
191
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
192
+ "dev": true,
193
+ "dependencies": {
194
+ "anymatch": "~3.1.2",
195
+ "braces": "~3.0.2",
196
+ "glob-parent": "~5.1.2",
197
+ "is-binary-path": "~2.1.0",
198
+ "is-glob": "~4.0.1",
199
+ "normalize-path": "~3.0.0",
200
+ "readdirp": "~3.6.0"
201
+ },
202
+ "engines": {
203
+ "node": ">= 8.10.0"
204
+ },
205
+ "funding": {
206
+ "url": "https://paulmillr.com/funding/"
207
+ },
208
+ "optionalDependencies": {
209
+ "fsevents": "~2.3.2"
210
+ }
211
+ },
212
+ "node_modules/concat-map": {
213
+ "version": "0.0.1",
214
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
215
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
216
+ "dev": true
217
+ },
218
+ "node_modules/content-disposition": {
219
+ "version": "0.5.4",
220
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
221
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
222
+ "dependencies": {
223
+ "safe-buffer": "5.2.1"
224
+ },
225
+ "engines": {
226
+ "node": ">= 0.6"
227
+ }
228
+ },
229
+ "node_modules/content-type": {
230
+ "version": "1.0.5",
231
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
232
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
233
+ "engines": {
234
+ "node": ">= 0.6"
235
+ }
236
+ },
237
+ "node_modules/cookie-signature": {
238
+ "version": "1.0.6",
239
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
240
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
241
+ },
242
+ "node_modules/cors": {
243
+ "version": "2.8.5",
244
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
245
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
246
+ "dependencies": {
247
+ "object-assign": "^4",
248
+ "vary": "^1"
249
+ },
250
+ "engines": {
251
+ "node": ">= 0.10"
252
+ }
253
+ },
254
+ "node_modules/debug": {
255
+ "version": "4.3.5",
256
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
257
+ "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
258
+ "dev": true,
259
+ "dependencies": {
260
+ "ms": "2.1.2"
261
+ },
262
+ "engines": {
263
+ "node": ">=6.0"
264
+ },
265
+ "peerDependenciesMeta": {
266
+ "supports-color": {
267
+ "optional": true
268
+ }
269
+ }
270
+ },
271
+ "node_modules/define-data-property": {
272
+ "version": "1.1.4",
273
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
274
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
275
+ "dependencies": {
276
+ "es-define-property": "^1.0.0",
277
+ "es-errors": "^1.3.0",
278
+ "gopd": "^1.0.1"
279
+ },
280
+ "engines": {
281
+ "node": ">= 0.4"
282
+ },
283
+ "funding": {
284
+ "url": "https://github.com/sponsors/ljharb"
285
+ }
286
+ },
287
+ "node_modules/depd": {
288
+ "version": "2.0.0",
289
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
290
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
291
+ "engines": {
292
+ "node": ">= 0.8"
293
+ }
294
+ },
295
+ "node_modules/destroy": {
296
+ "version": "1.2.0",
297
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
298
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
299
+ "engines": {
300
+ "node": ">= 0.8",
301
+ "npm": "1.2.8000 || >= 1.4.16"
302
+ }
303
+ },
304
+ "node_modules/dotenv": {
305
+ "version": "16.4.5",
306
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
307
+ "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
308
+ "engines": {
309
+ "node": ">=12"
310
+ },
311
+ "funding": {
312
+ "url": "https://dotenvx.com"
313
+ }
314
+ },
315
+ "node_modules/ee-first": {
316
+ "version": "1.1.1",
317
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
318
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
319
+ },
320
+ "node_modules/encodeurl": {
321
+ "version": "1.0.2",
322
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
323
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
324
+ "engines": {
325
+ "node": ">= 0.8"
326
+ }
327
+ },
328
+ "node_modules/es-define-property": {
329
+ "version": "1.0.0",
330
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
331
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
332
+ "dependencies": {
333
+ "get-intrinsic": "^1.2.4"
334
+ },
335
+ "engines": {
336
+ "node": ">= 0.4"
337
+ }
338
+ },
339
+ "node_modules/es-errors": {
340
+ "version": "1.3.0",
341
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
342
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
343
+ "engines": {
344
+ "node": ">= 0.4"
345
+ }
346
+ },
347
+ "node_modules/escape-html": {
348
+ "version": "1.0.3",
349
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
350
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
351
+ },
352
+ "node_modules/etag": {
353
+ "version": "1.8.1",
354
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
355
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
356
+ "engines": {
357
+ "node": ">= 0.6"
358
+ }
359
+ },
360
+ "node_modules/express": {
361
+ "version": "4.19.2",
362
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
363
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
364
+ "dependencies": {
365
+ "accepts": "~1.3.8",
366
+ "array-flatten": "1.1.1",
367
+ "body-parser": "1.20.2",
368
+ "content-disposition": "0.5.4",
369
+ "content-type": "~1.0.4",
370
+ "cookie": "0.6.0",
371
+ "cookie-signature": "1.0.6",
372
+ "debug": "2.6.9",
373
+ "depd": "2.0.0",
374
+ "encodeurl": "~1.0.2",
375
+ "escape-html": "~1.0.3",
376
+ "etag": "~1.8.1",
377
+ "finalhandler": "1.2.0",
378
+ "fresh": "0.5.2",
379
+ "http-errors": "2.0.0",
380
+ "merge-descriptors": "1.0.1",
381
+ "methods": "~1.1.2",
382
+ "on-finished": "2.4.1",
383
+ "parseurl": "~1.3.3",
384
+ "path-to-regexp": "0.1.7",
385
+ "proxy-addr": "~2.0.7",
386
+ "qs": "6.11.0",
387
+ "range-parser": "~1.2.1",
388
+ "safe-buffer": "5.2.1",
389
+ "send": "0.18.0",
390
+ "serve-static": "1.15.0",
391
+ "setprototypeof": "1.2.0",
392
+ "statuses": "2.0.1",
393
+ "type-is": "~1.6.18",
394
+ "utils-merge": "1.0.1",
395
+ "vary": "~1.1.2"
396
+ },
397
+ "engines": {
398
+ "node": ">= 0.10.0"
399
+ }
400
+ },
401
+ "node_modules/express/node_modules/cookie": {
402
+ "version": "0.6.0",
403
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
404
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
405
+ "engines": {
406
+ "node": ">= 0.6"
407
+ }
408
+ },
409
+ "node_modules/express/node_modules/debug": {
410
+ "version": "2.6.9",
411
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
412
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
413
+ "dependencies": {
414
+ "ms": "2.0.0"
415
+ }
416
+ },
417
+ "node_modules/express/node_modules/ms": {
418
+ "version": "2.0.0",
419
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
420
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
421
+ },
422
+ "node_modules/fill-range": {
423
+ "version": "7.1.1",
424
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
425
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
426
+ "dev": true,
427
+ "dependencies": {
428
+ "to-regex-range": "^5.0.1"
429
+ },
430
+ "engines": {
431
+ "node": ">=8"
432
+ }
433
+ },
434
+ "node_modules/finalhandler": {
435
+ "version": "1.2.0",
436
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
437
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
438
+ "dependencies": {
439
+ "debug": "2.6.9",
440
+ "encodeurl": "~1.0.2",
441
+ "escape-html": "~1.0.3",
442
+ "on-finished": "2.4.1",
443
+ "parseurl": "~1.3.3",
444
+ "statuses": "2.0.1",
445
+ "unpipe": "~1.0.0"
446
+ },
447
+ "engines": {
448
+ "node": ">= 0.8"
449
+ }
450
+ },
451
+ "node_modules/finalhandler/node_modules/debug": {
452
+ "version": "2.6.9",
453
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
454
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
455
+ "dependencies": {
456
+ "ms": "2.0.0"
457
+ }
458
+ },
459
+ "node_modules/finalhandler/node_modules/ms": {
460
+ "version": "2.0.0",
461
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
462
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
463
+ },
464
+ "node_modules/forwarded": {
465
+ "version": "0.2.0",
466
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
467
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
468
+ "engines": {
469
+ "node": ">= 0.6"
470
+ }
471
+ },
472
+ "node_modules/fresh": {
473
+ "version": "0.5.2",
474
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
475
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
476
+ "engines": {
477
+ "node": ">= 0.6"
478
+ }
479
+ },
480
+ "node_modules/fsevents": {
481
+ "version": "2.3.3",
482
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
483
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
484
+ "dev": true,
485
+ "hasInstallScript": true,
486
+ "optional": true,
487
+ "os": [
488
+ "darwin"
489
+ ],
490
+ "engines": {
491
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
492
+ }
493
+ },
494
+ "node_modules/function-bind": {
495
+ "version": "1.1.2",
496
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
497
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
498
+ "funding": {
499
+ "url": "https://github.com/sponsors/ljharb"
500
+ }
501
+ },
502
+ "node_modules/get-intrinsic": {
503
+ "version": "1.2.4",
504
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
505
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
506
+ "dependencies": {
507
+ "es-errors": "^1.3.0",
508
+ "function-bind": "^1.1.2",
509
+ "has-proto": "^1.0.1",
510
+ "has-symbols": "^1.0.3",
511
+ "hasown": "^2.0.0"
512
+ },
513
+ "engines": {
514
+ "node": ">= 0.4"
515
+ },
516
+ "funding": {
517
+ "url": "https://github.com/sponsors/ljharb"
518
+ }
519
+ },
520
+ "node_modules/glob-parent": {
521
+ "version": "5.1.2",
522
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
523
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
524
+ "dev": true,
525
+ "dependencies": {
526
+ "is-glob": "^4.0.1"
527
+ },
528
+ "engines": {
529
+ "node": ">= 6"
530
+ }
531
+ },
532
+ "node_modules/gopd": {
533
+ "version": "1.0.1",
534
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
535
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
536
+ "dependencies": {
537
+ "get-intrinsic": "^1.1.3"
538
+ },
539
+ "funding": {
540
+ "url": "https://github.com/sponsors/ljharb"
541
+ }
542
+ },
543
+ "node_modules/has-flag": {
544
+ "version": "3.0.0",
545
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
546
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
547
+ "dev": true,
548
+ "engines": {
549
+ "node": ">=4"
550
+ }
551
+ },
552
+ "node_modules/has-property-descriptors": {
553
+ "version": "1.0.2",
554
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
555
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
556
+ "dependencies": {
557
+ "es-define-property": "^1.0.0"
558
+ },
559
+ "funding": {
560
+ "url": "https://github.com/sponsors/ljharb"
561
+ }
562
+ },
563
+ "node_modules/has-proto": {
564
+ "version": "1.0.3",
565
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
566
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
567
+ "engines": {
568
+ "node": ">= 0.4"
569
+ },
570
+ "funding": {
571
+ "url": "https://github.com/sponsors/ljharb"
572
+ }
573
+ },
574
+ "node_modules/has-symbols": {
575
+ "version": "1.0.3",
576
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
577
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
578
+ "engines": {
579
+ "node": ">= 0.4"
580
+ },
581
+ "funding": {
582
+ "url": "https://github.com/sponsors/ljharb"
583
+ }
584
+ },
585
+ "node_modules/hasown": {
586
+ "version": "2.0.2",
587
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
588
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
589
+ "dependencies": {
590
+ "function-bind": "^1.1.2"
591
+ },
592
+ "engines": {
593
+ "node": ">= 0.4"
594
+ }
595
+ },
596
+ "node_modules/helmet": {
597
+ "version": "7.1.0",
598
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
599
+ "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==",
600
+ "engines": {
601
+ "node": ">=16.0.0"
602
+ }
603
+ },
604
+ "node_modules/http-errors": {
605
+ "version": "2.0.0",
606
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
607
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
608
+ "dependencies": {
609
+ "depd": "2.0.0",
610
+ "inherits": "2.0.4",
611
+ "setprototypeof": "1.2.0",
612
+ "statuses": "2.0.1",
613
+ "toidentifier": "1.0.1"
614
+ },
615
+ "engines": {
616
+ "node": ">= 0.8"
617
+ }
618
+ },
619
+ "node_modules/iconv-lite": {
620
+ "version": "0.4.24",
621
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
622
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
623
+ "dependencies": {
624
+ "safer-buffer": ">= 2.1.2 < 3"
625
+ },
626
+ "engines": {
627
+ "node": ">=0.10.0"
628
+ }
629
+ },
630
+ "node_modules/ignore-by-default": {
631
+ "version": "1.0.1",
632
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
633
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
634
+ "dev": true
635
+ },
636
+ "node_modules/inherits": {
637
+ "version": "2.0.4",
638
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
639
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
640
+ },
641
+ "node_modules/ipaddr.js": {
642
+ "version": "1.9.1",
643
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
644
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
645
+ "engines": {
646
+ "node": ">= 0.10"
647
+ }
648
+ },
649
+ "node_modules/is-binary-path": {
650
+ "version": "2.1.0",
651
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
652
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
653
+ "dev": true,
654
+ "dependencies": {
655
+ "binary-extensions": "^2.0.0"
656
+ },
657
+ "engines": {
658
+ "node": ">=8"
659
+ }
660
+ },
661
+ "node_modules/is-extglob": {
662
+ "version": "2.1.1",
663
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
664
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
665
+ "dev": true,
666
+ "engines": {
667
+ "node": ">=0.10.0"
668
+ }
669
+ },
670
+ "node_modules/is-glob": {
671
+ "version": "4.0.3",
672
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
673
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
674
+ "dev": true,
675
+ "dependencies": {
676
+ "is-extglob": "^2.1.1"
677
+ },
678
+ "engines": {
679
+ "node": ">=0.10.0"
680
+ }
681
+ },
682
+ "node_modules/is-number": {
683
+ "version": "7.0.0",
684
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
685
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
686
+ "dev": true,
687
+ "engines": {
688
+ "node": ">=0.12.0"
689
+ }
690
+ },
691
+ "node_modules/media-typer": {
692
+ "version": "0.3.0",
693
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
694
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
695
+ "engines": {
696
+ "node": ">= 0.6"
697
+ }
698
+ },
699
+ "node_modules/merge-descriptors": {
700
+ "version": "1.0.1",
701
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
702
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
703
+ },
704
+ "node_modules/methods": {
705
+ "version": "1.1.2",
706
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
707
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
708
+ "engines": {
709
+ "node": ">= 0.6"
710
+ }
711
+ },
712
+ "node_modules/mime": {
713
+ "version": "1.6.0",
714
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
715
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
716
+ "bin": {
717
+ "mime": "cli.js"
718
+ },
719
+ "engines": {
720
+ "node": ">=4"
721
+ }
722
+ },
723
+ "node_modules/mime-db": {
724
+ "version": "1.52.0",
725
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
726
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
727
+ "engines": {
728
+ "node": ">= 0.6"
729
+ }
730
+ },
731
+ "node_modules/mime-types": {
732
+ "version": "2.1.35",
733
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
734
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
735
+ "dependencies": {
736
+ "mime-db": "1.52.0"
737
+ },
738
+ "engines": {
739
+ "node": ">= 0.6"
740
+ }
741
+ },
742
+ "node_modules/minimatch": {
743
+ "version": "3.1.2",
744
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
745
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
746
+ "dev": true,
747
+ "dependencies": {
748
+ "brace-expansion": "^1.1.7"
749
+ },
750
+ "engines": {
751
+ "node": "*"
752
+ }
753
+ },
754
+ "node_modules/morgan": {
755
+ "version": "1.10.0",
756
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
757
+ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
758
+ "dependencies": {
759
+ "basic-auth": "~2.0.1",
760
+ "debug": "2.6.9",
761
+ "depd": "~2.0.0",
762
+ "on-finished": "~2.3.0",
763
+ "on-headers": "~1.0.2"
764
+ },
765
+ "engines": {
766
+ "node": ">= 0.8.0"
767
+ }
768
+ },
769
+ "node_modules/morgan/node_modules/debug": {
770
+ "version": "2.6.9",
771
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
772
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
773
+ "dependencies": {
774
+ "ms": "2.0.0"
775
+ }
776
+ },
777
+ "node_modules/morgan/node_modules/ms": {
778
+ "version": "2.0.0",
779
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
780
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
781
+ },
782
+ "node_modules/morgan/node_modules/on-finished": {
783
+ "version": "2.3.0",
784
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
785
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
786
+ "dependencies": {
787
+ "ee-first": "1.1.1"
788
+ },
789
+ "engines": {
790
+ "node": ">= 0.8"
791
+ }
792
+ },
793
+ "node_modules/ms": {
794
+ "version": "2.1.2",
795
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
796
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
797
+ "dev": true
798
+ },
799
+ "node_modules/negotiator": {
800
+ "version": "0.6.3",
801
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
802
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
803
+ "engines": {
804
+ "node": ">= 0.6"
805
+ }
806
+ },
807
+ "node_modules/node-gyp-build": {
808
+ "version": "4.8.1",
809
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
810
+ "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
811
+ "optional": true,
812
+ "bin": {
813
+ "node-gyp-build": "bin.js",
814
+ "node-gyp-build-optional": "optional.js",
815
+ "node-gyp-build-test": "build-test.js"
816
+ }
817
+ },
818
+ "node_modules/nodemon": {
819
+ "version": "3.1.3",
820
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz",
821
+ "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==",
822
+ "dev": true,
823
+ "dependencies": {
824
+ "chokidar": "^3.5.2",
825
+ "debug": "^4",
826
+ "ignore-by-default": "^1.0.1",
827
+ "minimatch": "^3.1.2",
828
+ "pstree.remy": "^1.1.8",
829
+ "semver": "^7.5.3",
830
+ "simple-update-notifier": "^2.0.0",
831
+ "supports-color": "^5.5.0",
832
+ "touch": "^3.1.0",
833
+ "undefsafe": "^2.0.5"
834
+ },
835
+ "bin": {
836
+ "nodemon": "bin/nodemon.js"
837
+ },
838
+ "engines": {
839
+ "node": ">=10"
840
+ },
841
+ "funding": {
842
+ "type": "opencollective",
843
+ "url": "https://opencollective.com/nodemon"
844
+ }
845
+ },
846
+ "node_modules/normalize-path": {
847
+ "version": "3.0.0",
848
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
849
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
850
+ "dev": true,
851
+ "engines": {
852
+ "node": ">=0.10.0"
853
+ }
854
+ },
855
+ "node_modules/object-assign": {
856
+ "version": "4.1.1",
857
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
858
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
859
+ "engines": {
860
+ "node": ">=0.10.0"
861
+ }
862
+ },
863
+ "node_modules/object-inspect": {
864
+ "version": "1.13.1",
865
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
866
+ "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
867
+ "funding": {
868
+ "url": "https://github.com/sponsors/ljharb"
869
+ }
870
+ },
871
+ "node_modules/on-finished": {
872
+ "version": "2.4.1",
873
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
874
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
875
+ "dependencies": {
876
+ "ee-first": "1.1.1"
877
+ },
878
+ "engines": {
879
+ "node": ">= 0.8"
880
+ }
881
+ },
882
+ "node_modules/on-headers": {
883
+ "version": "1.0.2",
884
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
885
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
886
+ "engines": {
887
+ "node": ">= 0.8"
888
+ }
889
+ },
890
+ "node_modules/parseurl": {
891
+ "version": "1.3.3",
892
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
893
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
894
+ "engines": {
895
+ "node": ">= 0.8"
896
+ }
897
+ },
898
+ "node_modules/path-to-regexp": {
899
+ "version": "0.1.7",
900
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
901
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
902
+ },
903
+ "node_modules/picomatch": {
904
+ "version": "2.3.1",
905
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
906
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
907
+ "dev": true,
908
+ "engines": {
909
+ "node": ">=8.6"
910
+ },
911
+ "funding": {
912
+ "url": "https://github.com/sponsors/jonschlinkert"
913
+ }
914
+ },
915
+ "node_modules/proxy-addr": {
916
+ "version": "2.0.7",
917
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
918
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
919
+ "dependencies": {
920
+ "forwarded": "0.2.0",
921
+ "ipaddr.js": "1.9.1"
922
+ },
923
+ "engines": {
924
+ "node": ">= 0.10"
925
+ }
926
+ },
927
+ "node_modules/pstree.remy": {
928
+ "version": "1.1.8",
929
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
930
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
931
+ "dev": true
932
+ },
933
+ "node_modules/qs": {
934
+ "version": "6.11.0",
935
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
936
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
937
+ "dependencies": {
938
+ "side-channel": "^1.0.4"
939
+ },
940
+ "engines": {
941
+ "node": ">=0.6"
942
+ },
943
+ "funding": {
944
+ "url": "https://github.com/sponsors/ljharb"
945
+ }
946
+ },
947
+ "node_modules/range-parser": {
948
+ "version": "1.2.1",
949
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
950
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
951
+ "engines": {
952
+ "node": ">= 0.6"
953
+ }
954
+ },
955
+ "node_modules/raw-body": {
956
+ "version": "2.5.2",
957
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
958
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
959
+ "dependencies": {
960
+ "bytes": "3.1.2",
961
+ "http-errors": "2.0.0",
962
+ "iconv-lite": "0.4.24",
963
+ "unpipe": "1.0.0"
964
+ },
965
+ "engines": {
966
+ "node": ">= 0.8"
967
+ }
968
+ },
969
+ "node_modules/readdirp": {
970
+ "version": "3.6.0",
971
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
972
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
973
+ "dev": true,
974
+ "dependencies": {
975
+ "picomatch": "^2.2.1"
976
+ },
977
+ "engines": {
978
+ "node": ">=8.10.0"
979
+ }
980
+ },
981
+ "node_modules/safe-buffer": {
982
+ "version": "5.2.1",
983
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
984
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
985
+ "funding": [
986
+ {
987
+ "type": "github",
988
+ "url": "https://github.com/sponsors/feross"
989
+ },
990
+ {
991
+ "type": "patreon",
992
+ "url": "https://www.patreon.com/feross"
993
+ },
994
+ {
995
+ "type": "consulting",
996
+ "url": "https://feross.org/support"
997
+ }
998
+ ]
999
+ },
1000
+ "node_modules/safer-buffer": {
1001
+ "version": "2.1.2",
1002
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1003
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1004
+ },
1005
+ "node_modules/semver": {
1006
+ "version": "7.6.2",
1007
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
1008
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
1009
+ "dev": true,
1010
+ "bin": {
1011
+ "semver": "bin/semver.js"
1012
+ },
1013
+ "engines": {
1014
+ "node": ">=10"
1015
+ }
1016
+ },
1017
+ "node_modules/send": {
1018
+ "version": "0.18.0",
1019
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
1020
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
1021
+ "dependencies": {
1022
+ "debug": "2.6.9",
1023
+ "depd": "2.0.0",
1024
+ "destroy": "1.2.0",
1025
+ "encodeurl": "~1.0.2",
1026
+ "escape-html": "~1.0.3",
1027
+ "etag": "~1.8.1",
1028
+ "fresh": "0.5.2",
1029
+ "http-errors": "2.0.0",
1030
+ "mime": "1.6.0",
1031
+ "ms": "2.1.3",
1032
+ "on-finished": "2.4.1",
1033
+ "range-parser": "~1.2.1",
1034
+ "statuses": "2.0.1"
1035
+ },
1036
+ "engines": {
1037
+ "node": ">= 0.8.0"
1038
+ }
1039
+ },
1040
+ "node_modules/send/node_modules/debug": {
1041
+ "version": "2.6.9",
1042
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1043
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1044
+ "dependencies": {
1045
+ "ms": "2.0.0"
1046
+ }
1047
+ },
1048
+ "node_modules/send/node_modules/debug/node_modules/ms": {
1049
+ "version": "2.0.0",
1050
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1051
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
1052
+ },
1053
+ "node_modules/send/node_modules/ms": {
1054
+ "version": "2.1.3",
1055
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1056
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1057
+ },
1058
+ "node_modules/serve-static": {
1059
+ "version": "1.15.0",
1060
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1061
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1062
+ "dependencies": {
1063
+ "encodeurl": "~1.0.2",
1064
+ "escape-html": "~1.0.3",
1065
+ "parseurl": "~1.3.3",
1066
+ "send": "0.18.0"
1067
+ },
1068
+ "engines": {
1069
+ "node": ">= 0.8.0"
1070
+ }
1071
+ },
1072
+ "node_modules/set-function-length": {
1073
+ "version": "1.2.2",
1074
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
1075
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
1076
+ "dependencies": {
1077
+ "define-data-property": "^1.1.4",
1078
+ "es-errors": "^1.3.0",
1079
+ "function-bind": "^1.1.2",
1080
+ "get-intrinsic": "^1.2.4",
1081
+ "gopd": "^1.0.1",
1082
+ "has-property-descriptors": "^1.0.2"
1083
+ },
1084
+ "engines": {
1085
+ "node": ">= 0.4"
1086
+ }
1087
+ },
1088
+ "node_modules/setprototypeof": {
1089
+ "version": "1.2.0",
1090
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1091
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1092
+ },
1093
+ "node_modules/side-channel": {
1094
+ "version": "1.0.6",
1095
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
1096
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
1097
+ "dependencies": {
1098
+ "call-bind": "^1.0.7",
1099
+ "es-errors": "^1.3.0",
1100
+ "get-intrinsic": "^1.2.4",
1101
+ "object-inspect": "^1.13.1"
1102
+ },
1103
+ "engines": {
1104
+ "node": ">= 0.4"
1105
+ },
1106
+ "funding": {
1107
+ "url": "https://github.com/sponsors/ljharb"
1108
+ }
1109
+ },
1110
+ "node_modules/simple-update-notifier": {
1111
+ "version": "2.0.0",
1112
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1113
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1114
+ "dev": true,
1115
+ "dependencies": {
1116
+ "semver": "^7.5.3"
1117
+ },
1118
+ "engines": {
1119
+ "node": ">=10"
1120
+ }
1121
+ },
1122
+ "node_modules/statuses": {
1123
+ "version": "2.0.1",
1124
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1125
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1126
+ "engines": {
1127
+ "node": ">= 0.8"
1128
+ }
1129
+ },
1130
+ "node_modules/supports-color": {
1131
+ "version": "5.5.0",
1132
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1133
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1134
+ "dev": true,
1135
+ "dependencies": {
1136
+ "has-flag": "^3.0.0"
1137
+ },
1138
+ "engines": {
1139
+ "node": ">=4"
1140
+ }
1141
+ },
1142
+ "node_modules/to-regex-range": {
1143
+ "version": "5.0.1",
1144
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1145
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1146
+ "dev": true,
1147
+ "dependencies": {
1148
+ "is-number": "^7.0.0"
1149
+ },
1150
+ "engines": {
1151
+ "node": ">=8.0"
1152
+ }
1153
+ },
1154
+ "node_modules/toidentifier": {
1155
+ "version": "1.0.1",
1156
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1157
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1158
+ "engines": {
1159
+ "node": ">=0.6"
1160
+ }
1161
+ },
1162
+ "node_modules/touch": {
1163
+ "version": "3.1.1",
1164
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1165
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1166
+ "dev": true,
1167
+ "bin": {
1168
+ "nodetouch": "bin/nodetouch.js"
1169
+ }
1170
+ },
1171
+ "node_modules/type-is": {
1172
+ "version": "1.6.18",
1173
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1174
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1175
+ "dependencies": {
1176
+ "media-typer": "0.3.0",
1177
+ "mime-types": "~2.1.24"
1178
+ },
1179
+ "engines": {
1180
+ "node": ">= 0.6"
1181
+ }
1182
+ },
1183
+ "node_modules/undefsafe": {
1184
+ "version": "2.0.5",
1185
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1186
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1187
+ "dev": true
1188
+ },
1189
+ "node_modules/unpipe": {
1190
+ "version": "1.0.0",
1191
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1192
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1193
+ "engines": {
1194
+ "node": ">= 0.8"
1195
+ }
1196
+ },
1197
+ "node_modules/utils-merge": {
1198
+ "version": "1.0.1",
1199
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1200
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1201
+ "engines": {
1202
+ "node": ">= 0.4.0"
1203
+ }
1204
+ },
1205
+ "node_modules/vary": {
1206
+ "version": "1.1.2",
1207
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1208
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1209
+ "engines": {
1210
+ "node": ">= 0.8"
1211
+ }
1212
+ },
1213
+ "node_modules/ws": {
1214
+ "version": "8.17.0",
1215
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
1216
+ "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
1217
+ "engines": {
1218
+ "node": ">=10.0.0"
1219
+ },
1220
+ "peerDependencies": {
1221
+ "bufferutil": "^4.0.1",
1222
+ "utf-8-validate": ">=5.0.2"
1223
+ },
1224
+ "peerDependenciesMeta": {
1225
+ "bufferutil": {
1226
+ "optional": true
1227
+ },
1228
+ "utf-8-validate": {
1229
+ "optional": true
1230
+ }
1231
+ }
1232
+ }
1233
+ }
1234
+ }
package.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "watchparty",
3
+ "version": "1.0.0",
4
+ "description": "Start a Stremio session with friends: watch together, chat and share controls. No addon sharing required.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "start": "node index.js",
9
+ "dev": "nodemon index.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/MateusAquino/WatchParty.git"
14
+ },
15
+ "keywords": [
16
+ "stremio",
17
+ "plugin",
18
+ "watchparty",
19
+ "betterstremio"
20
+ ],
21
+ "author": "MateusAquino",
22
+ "license": "MIT",
23
+ "bugs": {
24
+ "url": "https://github.com/MateusAquino/WatchParty/issues"
25
+ },
26
+ "homepage": "https://github.com/MateusAquino/WatchParty#readme",
27
+ "dependencies": {
28
+ "cors": "^2.8.5",
29
+ "dotenv": "^16.4.5",
30
+ "express": "^4.19.2",
31
+ "helmet": "^7.1.0",
32
+ "morgan": "^1.10.0",
33
+ "ws": "^8.17.0"
34
+ },
35
+ "devDependencies": {
36
+ "nodemon": "^3.1.3"
37
+ },
38
+ "optionalDependencies": {
39
+ "bufferutil": "^4.0.8"
40
+ }
41
+ }