From 9ffa8ad50b6ce5595b250576c010597273f5a209 Mon Sep 17 00:00:00 2001 From: enricop89 Date: Mon, 12 Sep 2022 10:04:52 +0100 Subject: [PATCH 01/28] - update readme --- README.md | 36 ++++++++++++++++++------------------ app.json | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index b3a9246..e275ccd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# OpenTok Broadcast Sample App for JavaScript +# Vonage Video API Broadcast Sample App for JavaScript Tokbox is now known as Vonage -This document describes how to use the OpenTok Broadcast Sample App for JavaScript. Through +This document describes how to use the Video API Broadcast Sample App for JavaScript. Through the exploration of this sample application, you will learn best practices for setting up and managing hosts, guests, and viewers in a web-based broadcasting application. -In the OpenTok Broadcast Sample App, the host is the individual who controls and publishes +In the Video API Broadcast Sample App, the host is the individual who controls and publishes the broadcast. The sample app supports up to 3 guests who can publish in the broadcast. The sample app also supports the following recommended numbers of viewers, based on the @@ -16,9 +16,9 @@ number of publishers in the broadcast: - 1 host, 2 guests: 100 viewers - 1 host, 1 guest: 150 viewers -The OpenTok live streaming feature lets you broadcast an OpenTok session to an HTTP live +The Vonage Video API live streaming feature lets you broadcast an Video API session to an HTTP live streaming (HLS) stream. More clients can simultaneously view this stream than can view -a live interactive OpenTok session. Also, clients that do not support WebRTC (such as Safari) +a live interactive Video API session. Also, clients that do not support WebRTC (such as Safari) can view the HLS stream. HLS playback is not supported in all browsers. However, there are a number of plugins, such as [Flowplayer](https://flowplayer.org/), that provide cross-browser support (using Flash Player in browsers that do not provide direct HLS support). @@ -36,27 +36,27 @@ This guide has the following sections: - [Prerequisites](#prerequisites): A checklist of everything you need to get started. - [Quick start](#quick-start): A step-by-step tutorial to help you quickly run the sample app. - [Exploring the code](#exploring-the-code): This describes the sample app code design, which - uses recommended best practices to implement the OpenTok Broadcast app features. + uses recommended best practices to implement the Video API Broadcast app features. ## Prerequisites -To be prepared to develop your OpenTok Broadcast app: +To be prepared to develop your Video API Broadcast app: 1. Review the [OpenTok.js](https://tokbox.com/developer/sdks/js/) requirements. -2. Your app will need an OpenTok **API Key** and **API Secret**, which you can get from +2. Your app will need an Video API **API Key** and **API Secret**, which you can get from the [OpenTok Developer Dashboard](https://dashboard.tokbox.com/). Set the API Key and API Secret in [config.json](./config.json). -To run the OpenTok Broadcast Sample App, run the following commands: +To run the Video API Broadcast Sample App, run the following commands: ```bash npm i npm start ``` -_**NOTE**: The OpenTok Developer Dashboard allows you to quickly run this sample program. For production deployment, you must generate the **Session ID** and **Token** values using one of the [OpenTok Server SDKs](https://tokbox.com/developer/sdks/server/)._ +_**NOTE**: The Video API Developer Dashboard allows you to quickly run this sample program. For production deployment, you must generate the **Session ID** and **Token** values using one of the [Video API Server SDKs](https://tokbox.com/developer/sdks/server/)._ -_**IMPORTANT:** In order to deploy an OpenTok Broadcast app, your web domain must use HTTPS._ +_**IMPORTANT:** In order to deploy an Video API Broadcast app, your web domain must use HTTPS._ ## Quick start @@ -111,14 +111,14 @@ sample app yourself. This allows you to customize the app as desired. - **[server.js](./server.js)**: The server configures the routes for the host, guests, and viewers. - **[opentok-api.js](./services/opentok-api.js)**: Configures the **Session ID**, **Token**, - and **API Key**, creates the OpenTok session, and generates tokens for hosts, guests, and + and **API Key**, creates the Video API session, and generates tokens for hosts, guests, and viewers. Set the API Key and API Secret in [config.json](./config.json). - **[broadcast-api.js](./services/broadcast-api.js)**: Starts and ends the broadcast. - **[host.js](./public/js/host.js)**: The host is the individual who controls and publishes the broadcast, but does not control audio or video for guests or viewers. The host uses the - OpenTok [Signaling API](https://www.tokbox.com/developer/guides/signaling/js/) to send the + Video API [Signaling API](https://www.tokbox.com/developer/guides/signaling/js/) to send the signals to all clients in the session. - **[guest.js](./public/js/guest.js)**: Guests can publish in the broadcast. They can control @@ -204,7 +204,7 @@ subscribe to the host stream and other guest streams, and publish audio and vide ### Viewer The functions in [viewer.js](./public/js/viewer.js) retrieve the credentials from the HTML, -connect to the session and subscribe after receiving a signal from the host indicating the broadcast has started, and monitor broadcast status. Once the broadcast begins, the viewer can see the host and guests. Each viewer uses the OpenTok [Signaling API](https://www.tokbox.com/developer/guides/signaling/js/) to receive the signals sent in the broadcast. +connect to the session and subscribe after receiving a signal from the host indicating the broadcast has started, and monitor broadcast status. Once the broadcast begins, the viewer can see the host and guests. Each viewer uses the Video API [Signaling API](https://www.tokbox.com/developer/guides/signaling/js/) to receive the signals sent in the broadcast. ### Host @@ -214,7 +214,7 @@ streams, create the URL for viewers to watch the broadcast, and signal broadcast host UI includes an input field to add an [RTMP stream](https://tokbox.com/developer/beta/rtmp-broadcast/), a button to start and end the broadcast, as well as a control to get a sharable link that can be distributed to all potential viewers to watch the CDN stream. The host makes calls to -the server, which calls the OpenTok API to start and end the broadcast. Once the broadcast ends, +the server, which calls the Video API API to start and end the broadcast. Once the broadcast ends, the client player will recognize an error event and display a message that the broadcast is over. For more information, see [Initialize, Connect, and Publish to a Session](https://tokbox.com/developer/concepts/connect-and-publish/). @@ -246,7 +246,7 @@ listeners for the broadcast button. When the broadcast button is clicked, the `startBroadcast()` method is invoked and submits a request to the server endpoint to begin the broadcast. The server endpoint relays the -session ID to the [OpenTok HLS Broadcast REST](https://tokbox.com/developer/rest/#start_broadcast) +session ID to the [Video API HLS Broadcast REST](https://tokbox.com/developer/rest/#start_broadcast) `/broadcast/start` endpoint, which returns broadcast data to the host. The broadcast data includes the broadcast URL in its JSON-encoded HTTP response: @@ -267,7 +267,7 @@ includes the broadcast URL in its JSON-encoded HTTP response: ``` The `startBroadcast()` method subsequently calls the `updateStatus()` method with the broadcast -status. The `updateStatus()` method uses the [OpenTok Signaling API](https://www.tokbox.com/developer/guides/signaling/js/) +status. The `updateStatus()` method uses the [Video API Signaling API](https://www.tokbox.com/developer/guides/signaling/js/) to notify the live viewers who are subscribed to the session that the broadcast has started: ```javascript @@ -299,7 +299,7 @@ immediately, or sets a timeout to play at the appropriate future time: ``` When the broadcast is over, the `endBroadcast()` method in host.js submits a request to the server, -which invokes the [OpenTok Broadcast API](https://tokbox.com/developer/rest/#stop_broadcast) `/broadcast/stop` +which invokes the [Video API Broadcast API](https://tokbox.com/developer/rest/#stop_broadcast) `/broadcast/stop` endpoint, which terminates the CDN stream. This is a recommended best practice, as the default is that broadcasts remain active until a 120-minute timeout period has completed. diff --git a/app.json b/app.json index d639a96..d2b80f4 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "name": "Broadcast Sample App", - "description": "This sample app shows how to use the OpenTok HLS Broadcast functionality in a JavaScript app.", + "description": "This sample app shows how to use the Vonage Video API HLS Broadcast functionality in a JavaScript app.", "website": "https://vonage.com", "repository": "https://github.com/opentok/broadcast-sample-app", "image": "https://assets.tokbox.com/img/vonage/Vonage_VideoAPI_black.svg", From 99fd53843211358588324edd947c7ad51a47a305 Mon Sep 17 00:00:00 2001 From: enricop89 Date: Mon, 12 Sep 2022 16:33:53 +0100 Subject: [PATCH 02/28] - first commit --- config.json | 5 +- package-lock.json | 3662 ++++++++++++++++++++++++++++++++++++++- package.json | 6 +- public/css/styles.css | 96 +- public/js/host.js | 21 +- services/opentok-api.js | 18 +- views/pages/host.ejs | 84 +- 7 files changed, 3825 insertions(+), 67 deletions(-) diff --git a/config.json b/config.json index 4902b54..2a093d2 100644 --- a/config.json +++ b/config.json @@ -1,4 +1,5 @@ { - "apiKey": "YOUR_API_KEY", - "apiSecret": "YOUR_API_SECRET" + "apiKey": "47570531", + "apiSecret": "f424f74d30e2f3da73b43781fc90cc99b9606117", + "broadcastDefaultResolution": "1280x720" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f6a562d..c986271 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,3665 @@ { "name": "dynamic-video-layouts", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "dynamic-video-layouts", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "ejs": "^3.1.5", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "opentok": "^2.10.0" + }, + "devDependencies": { + "eslint": "^7.9.0", + "eslint-config-airbnb": "^18.2.0", + "nodemon": "^2.0.4" + }, + "engines": { + "node": "10.16.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", + "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "dependencies": { + "string-width": "^3.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "dependencies": { + "jake": "^10.6.1" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-abstract": { + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.9.0.tgz", + "integrity": "sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.1.3", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^1.3.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz", + "integrity": "sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^14.2.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0", + "eslint-plugin-import": "^2.21.2", + "eslint-plugin-jsx-a11y": "^6.3.0", + "eslint-plugin-react": "^7.20.0", + "eslint-plugin-react-hooks": "^4 || ^3 || ^2.3.0 || ^1.7.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz", + "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.9", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0", + "eslint-plugin-import": "^2.21.2" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/espree": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/filelist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", + "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "dev": true, + "dependencies": { + "ini": "^1.3.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, + "dependencies": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dependencies": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dependencies": { + "mime-db": "1.44.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", + "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^4.0.0" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nonce": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nonce/-/nonce-1.0.4.tgz", + "integrity": "sha1-7nMCrejBvvR28wG4yR9cxRpIdhI=", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "has": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.entries/node_modules/es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opentok": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/opentok/-/opentok-2.10.0.tgz", + "integrity": "sha512-i1CMHXe4f0az2NLzE3zTeOk8p/DF35bbKV7riVMRUHry2HBQRqfKMWeepg+18rLOO1t6eXfDXhvj+0ozJ1UbGw==", + "dependencies": { + "jsonwebtoken": "^8.2.0", + "lodash": "^4.17.11", + "opentok-token": "^1.1.1", + "request": "^2.88.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/opentok-token": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/opentok-token/-/opentok-token-1.1.1.tgz", + "integrity": "sha512-/uMsmUMiGWT95zemuE9H3OWSb+1NcDmZSDzjum1oKk2KLLGkjqlPsEo8NWarH2q1rfd+cBWP8Pu+dwJS0uNHcg==", + "dependencies": { + "lodash": "^4.17.11", + "nonce": "^1.0.3", + "unix-timestamp": "^0.1.2" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", + "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", + "dev": true, + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", + "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend/node_modules/es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart/node_modules/es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/term-size": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dev": true, + "dependencies": { + "debug": "^2.2.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unix-timestamp": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unix-timestamp/-/unix-timestamp-0.1.2.tgz", + "integrity": "sha1-w3HeCSqxSRolxc48B1jSwl5748M=" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-notifier": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.1.tgz", + "integrity": "sha512-9y+Kds0+LoLG6yN802wVXoIfxYEwh3FlZwzMwpCZp62S2i1/Jzeqb9Eeeju3NSHccGGasfGlK5/vEHbAifYRDg==", + "dev": true, + "dependencies": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/update-notifier/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/update-notifier/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/widest-line/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.10.4", @@ -124,7 +3781,8 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "requires": {} }, "ajv": { "version": "6.12.5", diff --git a/package.json b/package.json index 745cb14..0ce3eac 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,12 @@ "version": "1.0.0", "description": "OpenTok Broadcast Sample Application", "engines": { - "node": "10.16.3" + "node": "10.16.3" }, "main": "index.js", "scripts": { "start": "node index.js", - "dev": "nodemon * node index.js", + "dev": "nodemon index.js", "lint": "eslint", "heroku-postbuild": "node .heroku/post-deploy.js" }, @@ -38,4 +38,4 @@ "eslint-config-airbnb": "^18.2.0", "nodemon": "^2.0.4" } -} +} \ No newline at end of file diff --git a/public/css/styles.css b/public/css/styles.css index 2bd7b04..d8d97e3 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,6 +1,33 @@ html, body { box-sizing: border-box; + width: 100%; + height: 100%; + margin: 0; +} + +.main-container-host { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; +} + +.left-container { + width: 65%; + min-width: 900px; + height: 100%; + flex: 0 0 auto; + display: flex; + flex-direction: column; +} + +.right-container { + background: rgba(136, 31, 255, 0.7); + flex: 1 0 0; + color: #ccc; + overflow-x: hidden; + overflow-y: scroll; } .main-container { @@ -8,10 +35,18 @@ body { height: 85vh; margin: 0 auto; padding: 10px; - border: 1px solid #59c9e3; border-radius: 6px; } +.left-container-header { + display: flex; + padding: 10px; + font-size: 18px; + background: #000; + color: #fff; + font-weight: 600; +} + .main-container.player { min-height: 535px; } @@ -22,18 +57,28 @@ body { } .video-container { - width: 100%; - height: 100%; + /* flex: none; + padding: 10px; + overflow: hidden; + width: 640px; + height: 360px; */ + flex: none; + padding: 10px; + height: calc(100% - 100px); display: flex; - justify-content: space-around; } -.video-container.wrap { +.video-container>* { + max-width: 640px; + max-height: 360px; +} + +/* .video-container.wrap { display: flex; flex-wrap: wrap; justify-content: flex-start; -} +} */ .video-container:empty { width: 0; @@ -151,28 +196,25 @@ body { .broadcast-controls-container { margin: 15px 0 10px 0; text-align: center; - color: #696868; + color: #fff; font-family: Arial, Helvetica, sans-serif; + display: flex; + flex-direction: column; } .broadcast-controls-container .rtmp-container { - width: 675px; margin: 0 auto; display: flex; flex-direction: column; justify-content: space-around; + padding: 2em; } -.broadcast-controls-container .rtmp-container span { - font-family: "Poiret One"; - font-style: italic; - color: #0099cc; +.broadcast-controls-container .rtmp-container div { font-weight: 600; - display: inline-block; - height: 25px; } -.broadcast-controls-container .rtmp-container span.active { +.broadcast-controls-container .rtmp-container div.active { color: #59c9e3; font-weight: 600; letter-spacing: 1.5px; @@ -194,12 +236,20 @@ body { width: 45%; font-size: 14px; padding: 0 10px; - margin: 5px 0; + margin: 5px; text-align: center; } .broadcast-controls-container .rtmp-container input::-webkit-input-placeholder { - color: lightgrey; + color: #fff; +} + +.rtmp-options { + display: flex; + justify-content: center; + align-items: center; + margin: 5px; + flex-direction: column; } .controls-container .url { @@ -293,17 +343,15 @@ body { } .btn-broadcast { - font-family: 'Poiret One', cursive; - background-color: #0099cc; - box-shadow: 0 0 0 1px #0099cc; + background-color: #333; color: #fff !important; cursor: pointer; - font-size: 16px; + font-size: 18px; display: inline-block; margin: 5px 0; - padding: 5px 10px; + padding: 5px 15px; border: 0; - border-radius: 0; + border-radius: 5px; -o-transition: all .5s ease-out; -webkit-transition: all .5s ease-out; -moz-transition: all .5s ease-out; @@ -367,4 +415,4 @@ body { .no-show { height: 0; width: 0; -} +} \ No newline at end of file diff --git a/public/js/host.js b/public/js/host.js index f501cd3..2fc3c5f 100644 --- a/public/js/host.js +++ b/public/js/host.js @@ -99,10 +99,9 @@ rtmpActive.classList.remove('hidden'); } } else { - startStopButton.classList.remove('active'); - startStopButton.innerHTML = 'Broadcast Over'; - startStopButton.disabled = true; - rtmpActive.classList.add('hidden'); + startStopButton.classList; + startStopButton.innerHTML = 'Start Broadcast'; + rtmpActive.classList.remove('hidden'); } signal(session, broadcast.status); @@ -128,6 +127,7 @@ if (serverDefined && !server.checkValidity()) { document.getElementById('rtmpLabel').classList.add('hidden'); + document.getElementById('rtmp-options').classList.add('hidden'); document.getElementById('rtmpError').innerHTML = invalidServerMessage; document.getElementById('rtmpError').classList.remove('hidden'); return null; @@ -135,18 +135,20 @@ if (serverDefined && !streamDefined) { document.getElementById('rtmpLabel').classList.add('hidden'); + document.getElementById('rtmp-options').classList.add('hidden'); document.getElementById('rtmpError').innerHTML = invalidStreamMessage; document.getElementById('rtmpError').classList.remove('hidden'); return null; } document.getElementById('rtmpLabel').classList.remove('hidden'); + document.getElementById('rtmp-options').classList.remove('hidden'); document.getElementById('rtmpError').classList.add('hidden'); return { serverUrl: server.value, streamName: stream.value }; }; const hideRtmpInput = function () { - ['rtmpLabel', 'rtmpError', 'rtmpServer', 'rtmpStream'].forEach(function (id) { + ['rtmpLabel', 'rtmpError', 'rtmpServer', 'rtmpStream', 'rtmp-options'].forEach(function (id) { document.getElementById(id).classList.add('hidden'); }); }; @@ -164,8 +166,15 @@ return; } + const HLS_LL = document.querySelector('#hls-ll').checked; + const HLS_HD = document.querySelector('#hls-HD').checked; + const HLS_DVR = document.querySelector('#hls-dvr').checked; + hideRtmpInput(); - http.post('/broadcast/start', { streams: broadcast.streams, rtmp: rtmp }) + http.post('/broadcast/start', { + streams: broadcast.streams, rtmp: rtmp, + fhd: HLS_HD, dvr: HLS_DVR, lowLatency: HLS_LL + }) .then(function (broadcastData) { broadcast = broadcastData; updateStatus(session, 'active'); diff --git a/services/opentok-api.js b/services/opentok-api.js index 6cd5f06..f32e4b0 100644 --- a/services/opentok-api.js +++ b/services/opentok-api.js @@ -134,10 +134,12 @@ const getCredentials = async (userType) => { * @param {String} [rmtp] - The (optional) RTMP stream url * @returns {Promise} {Object} Broadcast data, Reject => {Error}> */ -const startBroadcast = async (streams, rmtp) => { +const startBroadcast = async (streams, rmtp, fhd = false, dvr = false, lowLatency = false) => { return new Promise((resolve, reject) => { let layout; + let dvrConfig = dvr; + let lowLatencyConfig = lowLatency; if (streams > 3) { layout = { type: 'bestFit' @@ -149,8 +151,14 @@ const startBroadcast = async (streams, rmtp) => { stylesheet: customStyle, }; } + if (dvrConfig) { + lowLatencyConfig = false; // DVR and LL are not compatible + } const outputs = { - hls: {}, + hls: { + dvr: dvrConfig, + lowLatency: lowLatencyConfig + }, }; const sessionId = activeSession.sessionId; @@ -160,8 +168,12 @@ const startBroadcast = async (streams, rmtp) => { outputs.rmtp = rmtp; } + const resolution = fhd ? "1920x1080" : process.env.broadcastDefaultResolution ? process.env.broadcastDefaultResolution : "1280x720"; + + + try { - OT.startBroadcast(sessionId, { layout, outputs, }, function (err, broadcast) { + OT.startBroadcast(sessionId, { layout, outputs, resolution }, function (err, broadcast) { if (err) reject(err); activeBroadcast = { diff --git a/views/pages/host.ejs b/views/pages/host.ejs index 5f5a28d..4cfcdbf 100644 --- a/views/pages/host.ejs +++ b/views/pages/host.ejs @@ -3,42 +3,72 @@ <%- include('../partials/head') %> - - - + + + -
-
- -
-
-
-
- Want to stream to YouTube Live or Facebook Live? Add your RTMP Server URL and Stream - Name: - - -
- - +
+
+
+ Video Broadcast Streams
-
- -
-
+
-
+

-
+

From 29e35e9de419657446e453c75facee54238cb4f1 Mon Sep 17 00:00:00 2001 From: enricop89 Date: Tue, 13 Sep 2022 16:34:33 +0100 Subject: [PATCH 04/28] - add sample config --- .gitignore | 4 +++- config.json | 5 ----- config.sample.json | 5 +++++ 3 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 config.json create mode 100644 config.sample.json diff --git a/.gitignore b/.gitignore index b512c09..3795b2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules \ No newline at end of file +node_modules + +config.json \ No newline at end of file diff --git a/config.json b/config.json deleted file mode 100644 index 2a093d2..0000000 --- a/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "apiKey": "47570531", - "apiSecret": "f424f74d30e2f3da73b43781fc90cc99b9606117", - "broadcastDefaultResolution": "1280x720" -} \ No newline at end of file diff --git a/config.sample.json b/config.sample.json new file mode 100644 index 0000000..810fd2a --- /dev/null +++ b/config.sample.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "apiSecret": "", + "broadcastDefaultResolution": "1280x720" +} \ No newline at end of file From 87f893cc8667e5b8dc4d66c16090c96bc02e8b2e Mon Sep 17 00:00:00 2001 From: enricop89 Date: Wed, 14 Sep 2022 14:28:51 +0100 Subject: [PATCH 05/28] - added bootstrap --- views/partials/head.ejs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/views/partials/head.ejs b/views/partials/head.ejs index 8aca42a..299c5d5 100644 --- a/views/partials/head.ejs +++ b/views/partials/head.ejs @@ -4,7 +4,13 @@ + + - \ No newline at end of file + + \ No newline at end of file From 7f7780985999956dfe3515fab858c21d946d71b6 Mon Sep 17 00:00:00 2001 From: enricop89 Date: Wed, 14 Sep 2022 15:59:40 +0100 Subject: [PATCH 06/28] - add hls viewer --- index.js | 6 ++ public/js/hls-viewer.js | 146 +++++++++++++++++++++++++++++++++++++ public/js/host.js | 25 +++++++ services/opentok-api.js | 1 + views/pages/hls-viewer.ejs | 23 ++++++ 5 files changed, 201 insertions(+) create mode 100644 public/js/hls-viewer.js create mode 100644 views/pages/hls-viewer.ejs diff --git a/index.js b/index.js index beb314b..f3260c8 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,12 @@ app.get('/viewer', async (req, res) => { .catch(error => res.status(500).send(error)); }) +app.get('/hls-viewer', async (req, res) => { + opentok.getCredentials('viewer') + .then(credentials => res.render('pages/hls-viewer', { credentials: JSON.stringify(credentials) })) + .catch(error => res.status(500).send(error)); +}) + app.get('/host', (req, res) => { opentok.getCredentials('host') .then(credentials => res.render('pages/host', { credentials: JSON.stringify(credentials) })) diff --git a/public/js/hls-viewer.js b/public/js/hls-viewer.js new file mode 100644 index 0000000..2ff27dd --- /dev/null +++ b/public/js/hls-viewer.js @@ -0,0 +1,146 @@ +/* eslint-disable object-shorthand */ +(function () { + + /** + * Options for adding OpenTok publisher and subscriber video elements + */ + const insertOptions = { + width: '100%', + height: '100%', + showControls: false + }; + + /** + * Get our OpenTok API Key, Session ID, and Token from the JSON embedded + * in the HTML. + */ + const getCredentials = function () { + const el = document.getElementById('credentials'); + const credentials = JSON.parse(el.getAttribute('data')); + el.remove(); + return credentials; + }; + + /** + * Subscribe to a stream + * @returns {Object} A subscriber object + */ + // const subscribe = function (session, stream) { + // const name = stream.name; + // const insertMode = name === 'Host' ? 'before' : 'after'; + // const properties = Object.assign({ name: name, insertMode: insertMode }, insertOptions); + // return session.subscribe(stream, 'hostDivider', properties, function (error) { + // if (error) { + // console.log(error); + // } + // }); + // }; + + /** Ping the host to see if the broadcast has started */ + const checkBroadcastStatus = function (session) { + session.signal({ + type: 'broadcast', + data: 'status' + }); + }; + + /** + * Update the banner based on the status of the broadcast (active or ended) + */ + const updateBanner = function (status) { + const banner = document.getElementById('banner'); + const bannerText = document.getElementById('bannerText'); + + if (status === 'active') { + banner.classList.add('hidden'); + } else if (status === 'ended') { + bannerText.classList.add('red'); + bannerText.innerHTML = 'The Broadcast is Over'; + banner.classList.remove('hidden'); + } + }; + + /** + * Listen for events on the OpenTok session + */ + const setEventListeners = function (session) { + const streams = []; + const subscribers = []; + let broadcastActive = false; + + /** Subscribe to new streams as they are published */ + // session.on('streamCreated', function (event) { + // streams.push(event.stream); + // if (broadcastActive) { + // subscribers.push(subscribe(session, event.stream)); + // } + // if (streams.length > 3) { + // document.getElementById('videoContainer').classList.add('wrap'); + // } + // }); + + // session.on('streamDestroyed', function (event) { + // const index = streams.indexOf(event.stream); + // streams.splice(index, 1); + // if (streams.length < 4) { + // document.getElementById('videoContainer').classList.remove('wrap'); + // } + // }); + + /** Listen for a broadcast status update from the host */ + + session.on('signal:broadcast-url', function (event) { + console.log("signal:broadcast-url", event) + const broadcastUrl = event.data; + var video = document.getElementById('video'); + if (Hls.isSupported()) { + var hls = new Hls(); + console.log("signal:broadcast-url - ", broadcastUrl) + hls.loadSource(broadcastUrl); + hls.attachMedia(video); + hls.on(Hls.Events.MANIFEST_PARSED, function () { + video.play().then(_ => { + // Autoplay started! + }).catch(error => { + // Autoplay was prevented. + // Show a "Play" button so that user can start playback. + });; + + }); + } + else if (video.canPlayType('application/vnd.apple.mpegurl')) { + video.src = broadcastUrl; + video.addEventListener('canplay', function () { + video.play(); + }); + } + updateBanner('active'); + }); + }; + + const playVideo = function () { + var video = document.getElementById('video'); + if (video) { + video.play().catch(err => { + console.log("Play Video error", err) + }); + } + } + + const init = function () { + document.getElementById('play-video').addEventListener('click', playVideo); + const credentials = getCredentials(); + const props = { connectionEventsSuppressed: true }; + const session = OT.initSession(credentials.apiKey, credentials.sessionId, props); + setEventListeners(session); + session.connect(credentials.token, function (error) { + if (error) { + console.log(error); + } else { + checkBroadcastStatus(session); + } + }); + }; + + document.addEventListener('DOMContentLoaded', init); +}()); diff --git a/public/js/host.js b/public/js/host.js index 09c49cb..37f5664 100644 --- a/public/js/host.js +++ b/public/js/host.js @@ -67,6 +67,19 @@ }); }; + const signalBroadcastURL = function (session, url, to) { + console.log("signalBroadcastURL", to, url); + const signalData = Object.assign({}, { type: 'broadcast-url', data: url }, to ? { to } : {}); + console.log("signalBroadcastURL#1", signalData); + session.signal(signalData, function (error) { + if (error) { + console.log(['[signalBroadcastURL] - error (', error.code, '): ', error.message].join('')); + } else { + console.log('[signalBroadcastURL] - signal sent'); + } + }); + }; + /** * Construct the url for viewers to view the broadcast stream * @param {String} url The CDN url for the m3u8 video stream @@ -180,6 +193,7 @@ .then(function (broadcastData) { broadcast = broadcastData; updateStatus(session, 'active'); + signalBroadcastURL(session, broadcast.url, null); analytics.log('startBroadcast', 'variationSuccess'); }).catch(function (error) { console.log(error); @@ -369,6 +383,17 @@ } }); + session.on('connectionCreated', function (event) { + console.log("connectionCreated", event); + console.log("session", session); + if (event.connection.connectionId !== session.connection.id) { + // send signal to new connected users + if (broadcast.status === 'active') { + signalBroadcastURL(session, broadcast.url, event.connection.connectionId); + } + } + }); + document.getElementById('copyURL').addEventListener('click', function () { showCopiedNotice(); }); diff --git a/services/opentok-api.js b/services/opentok-api.js index 8f66a78..dd8b618 100644 --- a/services/opentok-api.js +++ b/services/opentok-api.js @@ -184,6 +184,7 @@ const startBroadcast = async (streams, rmtp, fhd = false, dvr = false, lowLatenc apiKey: apiKey, availableAt: broadcast.createdAt + broadcastDelay }; + console.log("activeBroadcast", activeBroadcast) resolve(activeBroadcast); }); } diff --git a/views/pages/hls-viewer.ejs b/views/pages/hls-viewer.ejs new file mode 100644 index 0000000..849b6db --- /dev/null +++ b/views/pages/hls-viewer.ejs @@ -0,0 +1,23 @@ + + + + + <%- include('../partials/head') %> + + + + + + +
+ +
+ + +
+
+ + + \ No newline at end of file From a0a1a02dc738878962391d42794996b2f37a0626 Mon Sep 17 00:00:00 2001 From: enricop89 Date: Thu, 15 Sep 2022 15:46:49 +0100 Subject: [PATCH 07/28] - added HLS --- public/css/styles.css | 21 +++++++++++++++++++-- public/js/hls-viewer.js | 21 +++++++++++++-------- views/pages/hls-viewer.ejs | 12 +++++++++--- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/public/css/styles.css b/public/css/styles.css index 377cd46..53de2ca 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -183,9 +183,8 @@ body { } .banner .text { - color: #0099cc; + color: #000; font-size: 34px; - font-family: 'Yellowtail', cursive; letter-spacing: 1px; } @@ -420,4 +419,22 @@ body { .no-show { height: 0; width: 0; +} + +#play-video-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); +} + +#play-video-container .button-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: block; + text-align: center; } \ No newline at end of file diff --git a/public/js/hls-viewer.js b/public/js/hls-viewer.js index 2ff27dd..6bccc3d 100644 --- a/public/js/hls-viewer.js +++ b/public/js/hls-viewer.js @@ -49,14 +49,20 @@ */ const updateBanner = function (status) { const banner = document.getElementById('banner'); + const videoContainer = document.getElementById('videoContainer'); const bannerText = document.getElementById('bannerText'); + const playVideoContainer = document.getElementById('play-video-container'); if (status === 'active') { banner.classList.add('hidden'); + videoContainer.classList.remove('hidden'); + playVideoContainer.classList.remove('hidden'); } else if (status === 'ended') { bannerText.classList.add('red'); bannerText.innerHTML = 'The Broadcast is Over'; banner.classList.remove('hidden'); + videoContainer.classList.add('hidden'); + playVideoContainer.classList.add('hidden'); } }; @@ -90,7 +96,7 @@ /** Listen for a broadcast status update from the host */ session.on('signal:broadcast-url', function (event) { - console.log("signal:broadcast-url", event) + console.log("signal:broadcast-url", event); const broadcastUrl = event.data; var video = document.getElementById('video'); if (Hls.isSupported()) { @@ -99,12 +105,7 @@ hls.loadSource(broadcastUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function () { - video.play().then(_ => { - // Autoplay started! - }).catch(error => { - // Autoplay was prevented. - // Show a "Play" button so that user can start playback. - });; + playVideo() }); } @@ -121,7 +122,11 @@ const playVideo = function () { var video = document.getElementById('video'); if (video) { - video.play().catch(err => { + video.play().then((res) => { + console.log("Play Video Successfull", res); + const playVideoContainer = document.getElementById('play-video-container'); + playVideoContainer.classList.add('hidden'); + }).catch(err => { console.log("Play Video error", err) }); } diff --git a/views/pages/hls-viewer.ejs b/views/pages/hls-viewer.ejs index 849b6db..c909b2c 100644 --- a/views/pages/hls-viewer.ejs +++ b/views/pages/hls-viewer.ejs @@ -13,9 +13,15 @@ -
- - + +
From 7f02cbdcf247cdd5d80cbd8348bdbb4aaf2377fb Mon Sep 17 00:00:00 2001 From: enricop89 Date: Wed, 21 Sep 2022 12:27:28 +0100 Subject: [PATCH 08/28] - push --- public/js/hls-viewer.js | 13 ++++++++++++- views/pages/hls-viewer.ejs | 22 ++++++++++++++++++++++ views/partials/head.ejs | 10 ++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/public/js/hls-viewer.js b/public/js/hls-viewer.js index 6bccc3d..d8e0b4e 100644 --- a/public/js/hls-viewer.js +++ b/public/js/hls-viewer.js @@ -132,8 +132,17 @@ } } - const init = function () { + const switchToLiveMode = function () { + window.location.href = 'viewer.html' + } + + const addClickEventListeners = function () { document.getElementById('play-video').addEventListener('click', playVideo); + document.getElementById('go-live-btn').addEventListener('click', switchToLiveMode); + } + + const init = function () { + addClickEventListeners(); const credentials = getCredentials(); const props = { connectionEventsSuppressed: true }; const session = OT.initSession(credentials.apiKey, credentials.sessionId, props); @@ -147,5 +156,7 @@ }); }; + + document.addEventListener('DOMContentLoaded', init); }()); diff --git a/views/pages/hls-viewer.ejs b/views/pages/hls-viewer.ejs index c909b2c..dd15a37 100644 --- a/views/pages/hls-viewer.ejs +++ b/views/pages/hls-viewer.ejs @@ -15,6 +15,9 @@
+ \ No newline at end of file diff --git a/views/partials/head.ejs b/views/partials/head.ejs index 299c5d5..9355f55 100644 --- a/views/partials/head.ejs +++ b/views/partials/head.ejs @@ -11,6 +11,12 @@ - + + \ No newline at end of file From c769a9a2c643a22776b4af273dd93aed9f0ad994 Mon Sep 17 00:00:00 2001 From: Javier Molina Date: Thu, 22 Sep 2022 14:39:24 +0100 Subject: [PATCH 09/28] fixing UI glitches --- .gitignore | 4 +- README.md | 62 ++-- config.sample.json | 2 +- index.js | 80 +++-- public/css/styles.css | 534 ++++++++++++++++-------------- public/images/screenshare.png | Bin 0 -> 2222 bytes public/images/stopscreenshare.png | Bin 0 -> 4346 bytes public/js/hls-viewer.js | 85 ++--- public/js/host.js | 470 +++++++++++++++++++++----- public/js/viewer.js | 45 ++- services/opentok-api.js | 164 +++++---- views/pages/hls-viewer.ejs | 98 +++--- views/pages/host.ejs | 138 ++++---- views/pages/viewer.ejs | 74 ++++- views/partials/head.ejs | 49 ++- 15 files changed, 1156 insertions(+), 649 deletions(-) create mode 100644 public/images/screenshare.png create mode 100644 public/images/stopscreenshare.png diff --git a/.gitignore b/.gitignore index 3795b2d..a2f0b2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules - -config.json \ No newline at end of file +.DS_STORE +config.json diff --git a/README.md b/README.md index e275ccd..c055a91 100644 --- a/README.md +++ b/README.md @@ -12,22 +12,23 @@ the broadcast. The sample app supports up to 3 guests who can publish in the bro The sample app also supports the following recommended numbers of viewers, based on the number of publishers in the broadcast: -- 1 host, 3 guests: 75 viewers -- 1 host, 2 guests: 100 viewers -- 1 host, 1 guest: 150 viewers +- 1 host, 3 guests: 11000 viewers +- 1 host, 2 guests: 13000 viewers +- 1 host, 1 guest: 15000 viewers The Vonage Video API live streaming feature lets you broadcast an Video API session to an HTTP live streaming (HLS) stream. More clients can simultaneously view this stream than can view -a live interactive Video API session. Also, clients that do not support WebRTC (such as Safari) +a live interactive Video API session. Also, clients that do not support WebRTC can view the HLS stream. HLS playback is not supported in all browsers. However, there are a number of plugins, such as [Flowplayer](https://flowplayer.org/), that provide cross-browser support (using Flash Player in browsers that do not provide direct HLS support). +The DVR feature provides a two-hour window for playing back broadcast content. While the broadcast is in progress, you can play back (and rewind to) any point in the broadcast up to two hours prior to the current time. The DVR recording is unavailable two hours after the broadcast is stopped. + **NOTE**: The viewer limits do not apply to HLS, since all publishing streams are transcoded to a single HLS stream that can be accessed from an HLS player. The expected latency for HLS -is 10-15 seconds. When the host clicks the broadcast button, a link is provided, which the -host can then share with all prospective viewers. The link directs the viewer to another page -within the application that streams the broadcast feed. +is 10-15 seconds and for low latency HLS is shorter. The host can select different options to start the broadcast (Full HD, Low latency and DVR). +The viewers can move back and forth from the HLS viewer view to the WebRTC view. You can configure and run this sample app within just a few minutes! @@ -74,7 +75,7 @@ To try out the Broadcast Sample App, visit the following URLs: Host: [https://broadcast-sample.herokuapp.com/host](https://broadcast-sample.herokuapp.com/host) Guest: [https://broadcast-sample.herokuapp.com/guest](https://broadcast-sample.herokuapp.com/guest) -Viewer: [https://broadcast-sample.herokuapp.com/viewer](https://broadcast-sample.herokuapp.com/viewer) +Viewer: [https://broadcast-sample.herokuapp.com/viewer](https://broadcast-sample.herokuapp.com/viewer) ### Starting a broadcast @@ -140,8 +141,7 @@ the credentials and creates the token for each user type (moderator, publisher, defined in [opentok-api.js](./services/opentok-api.js): ```javascript -const tokenOptions = userType => { - +const tokenOptions = (userType) => { const role = { host: 'moderator', guest: 'publisher', @@ -157,11 +157,14 @@ route is configured in server.js: ```javascript app.get('/host', (req, res) => { - api.getCredentials('host') - .then(credentials => res.render('pages/host', { - credentials: JSON.stringify(credentials) - })) - .catch(error => res.status(500).send(error)); + api + .getCredentials('host') + .then((credentials) => + res.render('pages/host', { + credentials: JSON.stringify(credentials), + }) + ) + .catch((error) => res.status(500).send(error)); }); ``` @@ -237,11 +240,11 @@ custom UI with controls for the publisher role associated with the host, and set listeners for the broadcast button. ```javascript - var publishAndSubscribe = function (session, publisher) { - session.publish(publisher); - addPublisherControls(publisher); - setEventListeners(session, publisher); - }; +var publishAndSubscribe = function (session, publisher) { + session.publish(publisher); + addPublisherControls(publisher); + setEventListeners(session, publisher); +}; ``` When the broadcast button is clicked, the `startBroadcast()` method is invoked and submits @@ -285,17 +288,16 @@ this timestamp to the current time to determine when to play the video. It eithe immediately, or sets a timeout to play at the appropriate future time: ```javascript - var init = function () { - - var broadcast = getBroadcastData(); - if (broadcast.availableAt <= Date.now()) { +var init = function () { + var broadcast = getBroadcastData(); + if (broadcast.availableAt <= Date.now()) { + play(broadcast.url); + } else { + setTimeout(function () { play(broadcast.url); - } else { - setTimeout(function () { play(broadcast.url); }, - broadcast.availableAt - Date.now()); - } - - }; + }, broadcast.availableAt - Date.now()); + } +}; ``` When the broadcast is over, the `endBroadcast()` method in host.js submits a request to the server, diff --git a/config.sample.json b/config.sample.json index 810fd2a..25a91a2 100644 --- a/config.sample.json +++ b/config.sample.json @@ -2,4 +2,4 @@ "apiKey": "", "apiSecret": "", "broadcastDefaultResolution": "1280x720" -} \ No newline at end of file +} diff --git a/index.js b/index.js index f3260c8..4792a5c 100644 --- a/index.js +++ b/index.js @@ -15,34 +15,50 @@ app.get('/', (req, res) => { res.redirect('/viewer'); }); +app.get('/host', (req, res) => { + opentok + .getCredentials('host') + .then((credentials) => + res.render('pages/host', { credentials: JSON.stringify(credentials) }) + ) + .catch((error) => res.status(500).send(error)); +}); + app.get('/viewer', async (req, res) => { - opentok.getCredentials('viewer') - .then(credentials => res.render('pages/viewer', { credentials: JSON.stringify(credentials) })) - .catch(error => res.status(500).send(error)); -}) + opentok + .getCredentials('viewer') + .then((credentials) => + res.render('pages/viewer', { credentials: JSON.stringify(credentials) }) + ) + .catch((error) => res.status(500).send(error)); +}); app.get('/hls-viewer', async (req, res) => { - opentok.getCredentials('viewer') - .then(credentials => res.render('pages/hls-viewer', { credentials: JSON.stringify(credentials) })) - .catch(error => res.status(500).send(error)); -}) - -app.get('/host', (req, res) => { - opentok.getCredentials('host') - .then(credentials => res.render('pages/host', { credentials: JSON.stringify(credentials) })) - .catch(error => res.status(500).send(error)); + opentok + .getCredentials('viewer') + .then((credentials) => + res.render('pages/hls-viewer', { + credentials: JSON.stringify(credentials), + }) + ) + .catch((error) => res.status(500).send(error)); }); app.get('/guest', (req, res) => { - opentok.getCredentials('guest') - .then(credentials => res.render('pages/guest', { credentials: JSON.stringify(credentials) })) - .catch(error => res.status(500).send(error)); + opentok + .getCredentials('guest') + .then((credentials) => + res.render('pages/guest', { credentials: JSON.stringify(credentials) }) + ) + .catch((error) => res.status(500).send(error)); }); app.get('/broadcast', (req, res) => { const url = req.query.url; const availableAt = req.query.availableAt; - res.render('pages/broadcast', { broadcast: JSON.stringify({ url, availableAt }) }); + res.render('pages/broadcast', { + broadcast: JSON.stringify({ url, availableAt }), + }); }); app.get('*', (req, res) => { @@ -53,30 +69,34 @@ app.get('*', (req, res) => { * API Endpoints */ app.post('/broadcast/start', (req, res) => { - const { streams, rtmp } = req.body; - opentok.startBroadcast(streams, rtmp) - .then(data => res.send(data)) - .catch(error => res.status(500).send(error)); + const { streams, rtmp, lowLatency, fhd, dvr } = req.body; + opentok + .startBroadcast(streams, rtmp, lowLatency, fhd, dvr) + .then((data) => res.send(data)) + .catch((error) => res.status(500).send(error)); }); app.post('/broadcast/layout', (req, res) => { const { streams, type } = req.body; - opentok.updateLayout(streams, type) - .then(data => res.status(200).send({})) - .catch(error => res.status(500).send(error)); + opentok + .updateLayout(streams, type) + .then((data) => res.status(200).send({})) + .catch((error) => res.status(500).send(error)); }); app.post('/broadcast/classes', (req, res) => { const { classList } = req.body; - opentok.updateStreamClassList(classList) - .then(data => res.status(200).send({})) - .catch(error => res.status(500).send(error)); + opentok + .updateStreamClassList(classList) + .then((data) => res.status(200).send({})) + .catch((error) => res.status(500).send(error)); }); app.post('/broadcast/end', (req, res) => { - opentok.stopBroadcast() - .then(data => res.send(data)) - .catch(error => res.status(500).send(error)); + opentok + .stopBroadcast() + .then((data) => res.send(data)) + .catch((error) => res.status(500).send(error)); }); /* diff --git a/public/css/styles.css b/public/css/styles.css index 53de2ca..ab40e63 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,77 +1,90 @@ html, body { - box-sizing: border-box; - width: 100%; - height: 100%; - margin: 0; + box-sizing: border-box; + width: 100%; + height: 100%; + margin: 0; } .main-container-host { - width: 100%; - height: 100%; - display: flex; - flex-direction: row; + width: 100%; + height: 100%; + display: flex; + flex-direction: row; } .left-container { - width: 65%; - min-width: 900px; - height: 100%; - flex: 0 0 auto; - display: flex; - flex-direction: column; + width: 65%; + min-width: 900px; + height: 100%; + flex: 0 0 auto; + display: flex; + flex-direction: column; } .right-container { - background: rgba(136, 31, 255, 0.7); - flex: 1 0 0; - color: #ccc; - overflow-x: hidden; - overflow-y: scroll; + background: rgba(136, 31, 255, 0.7); + flex: 1 0 0; + color: #ccc; + overflow-x: hidden; + overflow-y: scroll; +} + +#controls { + background-color: black; + height: 90px; + width: 100vw; + display: flex; + justify-content: center; + padding: 0; + margin: 0; + border-radius: 0; + border: none; + position: absolute; + bottom: 0; } .main-container { - width: 90vw; - height: 85vh; - margin: 0 auto; - padding: 10px; - border-radius: 6px; + width: 90vw; + height: 85vh; + margin: 0 auto; + padding: 10px; + border-radius: 6px; } .left-container-header { - display: flex; - padding: 10px; - font-size: 18px; - background: #000; - color: #fff; - font-weight: 600; + display: flex; + padding: 10px; + font-size: 18px; + background: #000; + color: #fff; + font-weight: 600; } .main-container.player { - min-height: 535px; + min-height: 535px; } .video-wrap { - height: 100%; - display: flex; + height: 100%; + display: flex; } .video-container { - /* flex: none; + /* flex: none; padding: 10px; overflow: hidden; width: 640px; height: 360px; */ - flex: none; - padding: 10px; - height: calc(100% - 100px); - display: flex; - + flex: none; + padding: 10px; + height: calc(100% - 100px); + display: flex; } -.video-container>* { - max-width: 640px; - max-height: 360px; +.video-container > * { + max-width: 640px; + max-height: 360px; } /* .video-container.wrap { @@ -81,13 +94,13 @@ body { } */ .video-container:empty { - width: 0; + width: 0; } .fp-player { - display: flex; - justify-content: center; - align-content: center; + display: flex; + justify-content: center; + align-content: center; } .fp-ratio, @@ -95,346 +108,359 @@ body { .fp-share, .fp-subtitle.audio-control, .fp-context-menu { - display: none !important; + display: none !important; } .video-container .OT_publisher, .video-container .OT_subscriber { - position: relative; - margin: 0 5px; + position: relative; + margin: 0 5px; } .video-container.wrap .OT_publisher, .video-container.wrap .OT_subscriber { - height: 49.5% !important; - width: 48.5% !important; + height: 49.5% !important; + width: 48.5% !important; } - .OT_publisher .OT_name.OT_edge-bar-item.OT_mode-off, .OT_subscriber .OT_name.OT_edge-bar-item.OT_mode-off { - position: absolute; - left: 0; - right: 0; - top: auto; - bottom: 20px; - margin: 0 auto; - opacity: 1; - font-family: 'Poiret One', cursive; - text-align: center; - background-color: rgba(0, 0, 0, 0.5); - width: 125px; - border-radius: 20px; + position: absolute; + left: 0; + right: 0; + top: auto; + bottom: 20px; + margin: 0 auto; + opacity: 1; + font-family: 'Poiret One', cursive; + text-align: center; + background-color: rgba(0, 0, 0, 0.5); + width: 125px; + border-radius: 20px; } .OT_widget-container .OT_video-element { - border-radius: 8px; + border-radius: 8px; } .publisher-controls-container { - height: 70px; - width: 36px; - background-color: rgba(0, 0, 0, 0.4); - z-index: 1001; - position: absolute; - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: center; - top: 20px; - left: 20px; - padding: 10px 0 !important; - border-radius: 20px; - box-sizing: border-box; + height: 70px; + width: 36px; + background-color: rgba(0, 0, 0, 0.4); + z-index: 1001; + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + top: 20px; + left: 20px; + padding: 10px 0 !important; + border-radius: 20px; + box-sizing: border-box; +} +.publisher-controls-container .screenshare { + height: 10px; } .publisher-controls-container .control { - background-size: contain; - background-repeat: no-repeat; - height: 16px; - width: 16px; - cursor: pointer; + background-size: contain; + background-repeat: no-repeat; + height: 16px; + width: 16px; + cursor: pointer; } .publisher-controls-container .video-control { - background-image: url('../images/video-icon.png'); + background-image: url('../images/video-icon.png'); +} +.publisher-controls-container .screenshare { + background-image: url('../images/screenshare.png'); +} + +.publisher-controls-container .screenshare.disabled { + background-image: url('../images/stopscreenshare.png'); } .publisher-controls-container .video-control.disabled { - background-image: url('../images/no-video-icon.png'); + background-image: url('../images/no-video-icon.png'); } .publisher-controls-container .audio-control { - background-image: url('../images/mic.png'); + background-image: url('../images/mic.png'); } .publisher-controls-container .audio-control.disabled { - background-image: url('../images/muted-mic.png'); + background-image: url('../images/muted-mic.png'); } .banner { - background-color: #fff; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - z-index: 1001; + background-color: #fff; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + z-index: 1001; } +.toggler { + height: 100px; + margin: auto; +} .banner .text { - color: #000; - font-size: 34px; - letter-spacing: 1px; + color: #000; + font-size: 34px; + letter-spacing: 1px; } .banner .text.red { - color: #E04E4E; + color: #e04e4e; } .broadcast-controls-container { - margin: 15px 0 10px 0; - text-align: center; - color: #fff; - font-family: Arial, Helvetica, sans-serif; - display: flex; - flex-direction: column; + margin: 15px 0 10px 0; + text-align: center; + color: #fff; + font-family: Arial, Helvetica, sans-serif; + display: flex; + flex-direction: column; } .broadcast-controls-container .rtmp-container { - margin: 0 auto; - display: flex; - flex-direction: column; - justify-content: space-around; - padding: 2em; + margin: 0 auto; + display: flex; + flex-direction: column; + justify-content: space-around; + padding: 2em; } .broadcast-controls-container .rtmp-container div { - font-weight: 600; + font-weight: 600; } .broadcast-controls-container .rtmp-container div.active { - color: #59c9e3; - font-weight: 600; - letter-spacing: 1.5px; + color: #59c9e3; + font-weight: 600; + letter-spacing: 1.5px; } .broadcast-controls-container .rtmp-container span.error { - color: red; - font-weight: 800; + color: red; + font-weight: 800; } .broadcast-controls-container .rtmp-container .input-container { - width: 100%; - display: flex; - justify-content: space-around; + width: 100%; + display: flex; + justify-content: space-around; } .broadcast-controls-container .rtmp-container .input-container input { - height: 35px; - width: 45%; - font-size: 14px; - padding: 0 10px; - margin: 5px; - text-align: center; + height: 35px; + width: 45%; + font-size: 14px; + padding: 0 10px; + margin: 5px; + text-align: center; } .broadcast-controls-container .rtmp-container input::-webkit-input-placeholder { - color: #000; + color: #000; } .rtmp-options { - display: flex; - justify-content: center; - align-items: center; - margin: 5px; - flex-direction: column; + display: flex; + justify-content: center; + align-items: center; + margin: 5px; + flex-direction: column; } .rtmp-options .options-input { - width: 200px; - text-align: left; + width: 200px; + text-align: left; } .controls-container .url { - width: 90%; - color: #E04E4E; - font-size: 16px; - display: inline-block; - border: 1px solid grey; - padding: 3px 5px; - border-radius: 3px; + width: 90%; + color: #e04e4e; + font-size: 16px; + display: inline-block; + border: 1px solid grey; + padding: 3px 5px; + border-radius: 3px; } .url-container { - width: 90vw; - margin: 0 auto; - position: relative; + width: 90vw; + margin: 0 auto; + position: relative; } .copy-link { - position: relative; - font-family: 'Poiret One', cursive; - color: #59c9e3; - width: 200px; - margin: 10px auto; - cursor: pointer; + position: relative; + font-family: 'Poiret One', cursive; + color: #59c9e3; + width: 200px; + margin: 10px auto; + cursor: pointer; } .copy-link::before { - content: ''; - height: 20px; - width: 20px; - position: absolute; - left: 5px; - background-image: url('../images/get-link.png'); - background-repeat: no-repeat; - background-size: contain; + content: ''; + height: 20px; + width: 20px; + position: absolute; + left: 5px; + background-image: url('../images/get-link.png'); + background-repeat: no-repeat; + background-size: contain; } .tooltip { - position: absolute; + position: absolute; } .tooltip.copy { - -o-transition: all .5s ease-out; - -webkit-transition: all .5s ease-out; - -moz-transition: all .5s ease-out; - -ms-transition: all .5s ease-out; - -kthtml-transition: all .5s ease-out; - transition: all .5s ease-out; - -webkit-box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.75); - -moz-box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.75); - box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.75); + -o-transition: all 0.5s ease-out; + -webkit-transition: all 0.5s ease-out; + -moz-transition: all 0.5s ease-out; + -ms-transition: all 0.5s ease-out; + -kthtml-transition: all 0.5s ease-out; + transition: all 0.5s ease-out; + -webkit-box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.75); + -moz-box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.75); + box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.75); } .tooltip.copy::before { - content: '\2714'; - height: 16px; - width: 16px; - font-size: 12px; - line-height: 16px; - color: #59c9e3; - margin-right: 10px; - border: 1px solid #59c9e3; - border-radius: 50%; + content: '\2714'; + height: 16px; + width: 16px; + font-size: 12px; + line-height: 16px; + color: #59c9e3; + margin-right: 10px; + border: 1px solid #59c9e3; + border-radius: 50%; } .url-container .tooltip.copy { - position: absolute; - color: #59c9e3; - background-color: #fff; - border-radius: 4px; - font-family: 'Poiret One', cursive; - width: 250px; - height: 40px; - top: -45px; - right: 0px; - left: 0px; - margin: 0 auto; - display: flex; - justify-content: center; - align-items: center; + position: absolute; + color: #59c9e3; + background-color: #fff; + border-radius: 4px; + font-family: 'Poiret One', cursive; + width: 250px; + height: 40px; + top: -45px; + right: 0px; + left: 0px; + margin: 0 auto; + display: flex; + justify-content: center; + align-items: center; } .url-container .tooltip.copy .triangle-down { - color: #fff; - text-shadow: 0 2px 2px rgba(0, 0, 0, 0.4), 0 1px 0px rgba(0, 0, 0, 0.5); - position: absolute; - right: 0; - left: 0; - bottom: -16px; + color: #fff; + text-shadow: 0 2px 2px rgba(0, 0, 0, 0.4), 0 1px 0px rgba(0, 0, 0, 0.5); + position: absolute; + right: 0; + left: 0; + bottom: -16px; } .btn-broadcast { - background-color: #333; - color: #fff !important; - cursor: pointer; - font-size: 18px; - display: inline-block; - margin: 5px 0; - padding: 5px 15px; - border: 0; - border-radius: 5px; - -o-transition: all .5s ease-out; - -webkit-transition: all .5s ease-out; - -moz-transition: all .5s ease-out; - -ms-transition: all .5s ease-out; - -kthtml-transition: all .5s ease-out; - transition: all .5s ease-out; + background-color: #333; + color: #fff !important; + cursor: pointer; + font-size: 18px; + display: inline-block; + margin: 5px 0; + padding: 5px 15px; + border: 0; + border-radius: 5px; + -o-transition: all 0.5s ease-out; + -webkit-transition: all 0.5s ease-out; + -moz-transition: all 0.5s ease-out; + -ms-transition: all 0.5s ease-out; + -kthtml-transition: all 0.5s ease-out; + transition: all 0.5s ease-out; } .btn-broadcast:hover { - text-decoration: none; - background-color: #007ba3; - box-shadow: 0 0 0 2px #0099cc + text-decoration: none; + background-color: #007ba3; + box-shadow: 0 0 0 2px #0099cc; } .btn-broadcast.active { - background-color: #E04E4E; - box-shadow: 0 0 0 1px #E04E4E; + background-color: #e04e4e; + box-shadow: 0 0 0 1px #e04e4e; } .btn-broadcast.active:hover { - background-color: #692B2B; - box-shadow: 0 0 0 2px #E04E4E + background-color: #692b2b; + box-shadow: 0 0 0 2px #e04e4e; } .btn-broadcast:disabled { - background-color: darkgrey; - box-shadow: 0 0 0 2px darkgrey; - cursor: default; + background-color: darkgrey; + box-shadow: 0 0 0 2px darkgrey; + cursor: default; } .btn-copy { - position: relative; - vertical-align: top; - display: inline-block; - height: 26px; - width: 26px; - cursor: pointer; - background-color: #eee; - background-image: linear-gradient(#fcfcfc, #eee); - border: 1px solid grey; - border-radius: 3px; + position: relative; + vertical-align: top; + display: inline-block; + height: 26px; + width: 26px; + cursor: pointer; + background-color: #eee; + background-image: linear-gradient(#fcfcfc, #eee); + border: 1px solid grey; + border-radius: 3px; } .btn-copy img { - height: 85%; - width: 85%; - position: absolute; - top: 2px; - left: 2px; + height: 85%; + width: 85%; + position: absolute; + top: 2px; + left: 2px; } .hidden { - display: none !important; + display: none !important; } .opacity-0 { - opacity: 0; - z-index: -1; + opacity: 0; + z-index: -1; } .no-show { - height: 0; - width: 0; + height: 0; + width: 0; } #play-video-container { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.4); + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); } #play-video-container .button-container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - display: block; - text-align: center; -} \ No newline at end of file + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: block; + text-align: center; +} diff --git a/public/images/screenshare.png b/public/images/screenshare.png new file mode 100644 index 0000000000000000000000000000000000000000..a32a0d99bae5491edbcbf619d2f7e158f1480ca9 GIT binary patch literal 2222 zcmai$c{J2rAHaXV8DkwwmKvdul8USuVT`SrsVq&_EFtpP2bIY>!&In;K{fHr$rHj1 z87(A{F$tBWELp}ds%Nx3n1@7`@zQzU_nh~f_kEvx?)TnvKi_liy?=b}lI7}*k(J&q z4FG^F4(ov5=sPz)hr!7^rsf*ahiU!QhZg6@Bhr#*z=9;3#g!lQ;)(d% z@=L29tc&BvEpHDku8P#DI=KA5tNTJq$RTtIUpZfRHOvD%C{e!MtG=CkfFOnxvx2O` zhCwT_rZ+);cl9M|z?ogE_L24bxX;-%@{MbSdi-OQw#UtiQEM?*$ZDj11T-QxH!z}N z8a&h&xKq3A$+Sv{TC}B9WO-15O4kPycCKmxkDeHzI4nj7W|(p1A(4=1Rsag`J}$AU zH0=JO>y@v-&)l`J8RV zcY6S@Nz)-`^B+oBC=uyKO}Nrl3s@7m*8JrIozmX3@GI@l@%%<(6D%*|I3N#+>GmC$ z3ec_HcB_YeWI0<7889;OLp2?1nxskbzzI5@)?R}41HJnryJK8uokWe#sx_F@Uu*>L$ zrQjJ3o)vV@nJ!vnrzx&rjr!Rv#g*zk^X0^FG@hknRfKFd&9=_!KM3}8db@70(*+Co znlIG&mlwi%(&M6)k7fj-%Os)NjtB)k4hg$O+#%ag6Hfv2wBeb`|4c1OrC)FYJ%L$R z4|p3oBbExP$J{pvSvC*ros0>q=(v$E-;05(`fq$`|zwj9AFbDO73D6uv9R z`15C3V5+R|h?ip#4GbIK&DJ=83jV4S=t{8f(}wm{pLl-4P)FZHvl}OiX{A2uB$@6q zSBKg6DMIlr4<`2_^0y+WiS8;n=~`j8K!(fUCK6YrCJy{WnIOZT_Y)zg;bxnNQ3x`e z`!~^cRciR7pC}8|V@(s3;W$}1*1}I}V;#3H(#Nl-kkXgS?8J3kJU92CVx;-GamVHi zBoH^hhfh0aqkaj;$FQG{8U*{QQcZnGCdYa{VBdekNX@!U6+xcmuCpFC^c{J8*${$L z9fGzv{rOO>Av;d%2{7zcvL2vHee37;V10r8Yhg-EX)@Np;B7)Jyz|oo6vU$9I8ypC zSgdQK^C510Kuym#{DrfCdzvP3HdAUfch+sM%H2!u6qf0Knztpl)m3MJ9nIbwEy@(NV zAR2I#vmQNR!tNl)7_RgCB*-ohb2GQzdu$0(hu{a-ROrV+v|!Yj6$$D*Yn~|qD%9t+ zUqnqJ)xXx-v4mP%&e>!5?I&pk%T5!kj zK9RZI`RSnyF-b6YmMlT;=xk#CZd<6cTIr7!aaj@qLEeINe2-lkIu(5rP!kjlP@=9A zTWmt*;||x-zA5cygmsYO$rbUT)Og)Bgs^T5c2u_UlHw$($y$;Uz{4Txk9;ZNu@gD+ ziS=V7H1e+}JH5Qi0fxA)6m)*A?$2wltg0L2mp=I#;c3Y^(j4%QV!@gQVK<>9Sq~rf z!s#=YYw+=|0ZcA0P3i5~{oEX2XX*2IK+B!Ajf>i!3#&c@KMoY{8PkhNx;wHwAqVkP znrXU$DMFc$-QeDvmHh+AMD4RVmwNzse&PY$@sU6)b*h@$EyZ$mR{&$ z&*MEO={tkrEC$UKOh%SYXrf$rQ>VLb91!Wx{aB^t=VEgUL)as#Yli@NH{WAZ-z+o% zmn|q(b+{8W^zEB~$fz&#T3ac!Mz>}g6A}Tv4xKBgm-)mfuJ=QqTu#lbgA=a{%kDE{ zuM53inM7^tqdc3nbq37q{2Y2C12rcR**N}=(KJY%e3X2xRn$U+I{j*l)y=40r6Wi; z7e6s8%!=A5vi=NP<#Uzq2LXh?Wpsky&d^~0)hiwX6GkQX$6o(#&!;yHR>vlwgc^MB z(Fuj819D0z_xq@{!S9%f*^FyJ*@U?thd3`+r7thA-yaC9xbR(tI2 literal 0 HcmV?d00001 diff --git a/public/images/stopscreenshare.png b/public/images/stopscreenshare.png new file mode 100644 index 0000000000000000000000000000000000000000..3a01c37bea90d9c0d27d60c71bcc02cefa8537f3 GIT binary patch literal 4346 zcma)Ai9b~9|9{SzW9DFtv6P)rL`qqb_BobvOQLdHWSK5nM2%#R*$74I)>UX3SG1@U zQMP6(OKvi%+a-!=D!EyP2#r~O$Nl{S-}8E%^L(Dq=Xu_r=lOh|=lz^>vi!DqX=xa1 z006W${^Gd}00PntdOvS2HzN{OGt-6PS=YOLsd-biS3U+hTk+T{NBk+z|>i7VqX$Il(v7r#Gl-$=vP zsg;89`gY4Ssyq6>KXi8p60rAQ74|cUo0O*Rwr*MvQ1vuF_}h5I%;I~wdLavg7pLy3 zcRyY?{oB}+D41aB|N2-+?1^dh=vdR8jW3$6k^z@`a@q9tlLkp{>*ks3YiDJ|Xn&d9 z%T%Xgw!IHRc&htr_%7l_HDy|<=HHdeFQh_g5;ftUYtFd3qA~|XdBSe)KE#!PYtP2@ zrnHv*T=WS^)j@9sF9!(qM{#0+?P>=hFl+4 z8G!t|E@aNSY(in0usl#P!El1Y8g8y(sKYT@?qZ(ANYc<`@ualj`gDUN%2>$ngOS6I zFr>(i(rhcGu@_L z4FPMs>V41XSZP5Y7A81EqvV8q;hwt*h07ve%umdiX82h~wimY6shK@*b!Xf+sf+P* z9S9(z1IAgtUtR)Mk$=glmeVi(W%2@9dhgrHvqU~M@_Dy5ESF3?F4A-&^F@ouk`YX4I00*+2)O+ zWJ7s)notJ=z5q@aR`^G-qFx+-7t2a=pb8ga*o@lU(+zZtu&B`NVQ*~wP3C6mbXo#| zD%8uK8%7sp8^PU^f}Ys;N-B-LoyYFod+68O3b5(6UZ5g~XJ=ism?{iE!OqKy32lQP zOonXr6TUbf0YQP2i^Z*7+`bUQ?e-gAhU`O4DF~Cd#ckNRaL)F*{E+WSoejfJx2FjU zF+l1Je(|)vt6wd0FyaL*dF_aWGeRN2D{Ko3pxbkOa~3M-x1Si=P%j$7gB%d|!&!|! z*JCbU?dO`#n$^TTJYf+Ak~@P|6GSJAEHJ_QrQG`}J1T%5-Eq3biLK_A7ZhQiYYM`7!WItB zA^;O>(E#`j2tBd@Ka%>qnHxfa0j+e4(2hD_i%=kVwiQGJU{e`M55Y47_#5rL_3<@K z0H5$JUeVe*gG2YOxcG>T@e(+K=+J8G(wm7928g&}8ddiUp5fEm*2l#$q`Bh%<$K zo<=1Kl#1xFz@0s}zTM}-nh2R74hCn)$Wt*Z;JE*juVL}rq}Okc5OE8{*9PZ}VNzyD z6ahAti!$TMkCO%QAFgMq2Hf~C5($gVfnNYR)He^2e?9%OFLW@vmnAe&Glt>i(C8lE zmxR{8wn8Q*{e72hotB3_(nA4GwRB{nX1&i`Ol5fZdjWO+F$xRr07Ykzq+uyueUwJs)dS#j3)v~XgZmA#?EuFUV!DD~i10akTo-+bfB z6TWQ6PWn3V1`nk0OS&*s;?nzLFbwiX+w1@t$HDkk)UHmRu#{qeDYA>3O#W+_fnC?+ zv&x*!IED!PFJL&%;;PyO&+N^Xuw_OwnIQnr<^?_2V(yW+s;J&oigz+2X?#`shTB%i z?AV@f`?0;wAN+8R(^bvEXNgaUJTdCLYYQ1L)h#>vcp{@wbA?&SZ4cTeASVJV64W`r zi}@_ zDb=rM)bS-nX4m1F7|=oDVv854XXvHj;lda&8s?qXcPvyIhQ|^o8o;^pz2$0+OwALo z0#2hP{~52vx?2YYx?1gjA#?)gpTFL7vf_{p>g;sDzbUqW;4mC%-{_&-9}A>Eb!Z)N z7DAT^d@NmDRn*u=$#~$@h5AwyVyzPoTt%Z6g4n zg9t)azx=+fE0xxa&|LO1BTlqvx0%uE=#+NDsBBux( znP~ck$%mOEnPdPOUk}pDc~6eafe9F1>lzensE@g@Q-5K%S_esWJOOp z*5$TC4F(KvSWNK%=xXJT$r~y4vC8p>nvcC_7aZv5=?*z0{#4g7*%`CCgAd*hxuLWA zd+Do`Bg5_vWc~V(e*~lJ$9yDT8%?-dGjBoY&>;k8tlx_SLsQRI>7yxXMvceDY9!W@ z*iXpy^baTO!)MLiti2?dPeEqErGok0MHf&3S--1>rqJH9yf?WLBIxDu(bD*$$^oW>?MRiZHm`hGjn^`VZ-5w6pL;&`?4_JF8T+JZ# zq-(Y6#@Xt1FL7u)LdU0{JN#};I2Jxr`SU(D+=@PjZ$+!us>tMT+KDY?>2`(fb*m0Y zccQ5@0A62~m8Bo_9Xo~4@!n?+6ZxE!#MJDhVv+Wv?re;Ebu$1~D4S{B>)1OsVx_cs zf=3qX1|6-dm!67$o*R8@I+}~;hdogUJ~mtA=TYSt0A0_)#EK>Dxzbg$Hn1lJyc3Z! zMa&<`qk8ge3SfIe{9#pOw#w5rsqmZ5e_pyE;kn>kmBXw9_MJrVmRn#k`%cZkd5BK! z+J-0E+Ai~$4B`ZP0GwwShVWLH0pWxK@QaM+yBU`l|1SvJZGp!=L(~vrUztb*z$8sr zK=I(~nt+M0z~OI4PH-Iau^I5FB%{UoU%fGpodTET%@{xSkabd!Qb`CWvQukf0u=>` z+3$S$ikAnLQq~p!odTSL#!eI%5;&8S#qvP15j)PW-dCPvVB^KOv$Rr(4L%u1L~giyQLjt$gy4Xu*!l3Wok53ha5y-C ze6jfa#Uy1RN0=L=&~TF>4k>W4QAKa;w?F;4^uK=u7d$hcF+{WX!7BA;`OG$r<0?k~ zBiTy|fT}4RkD#dUm%MerM=0gl;E>_b;xeLD$|$(k3uB-lRKCbihRJwl=N^HYI*JeVrzk65IEzqc>%vN>~b|JYyhy872)Ywo-V zJ&>D{iYLYeg|UL}~tg+A;Tm71|urLisK9*2kGdJ!`s{3UX|fMzFKrehp?T);b&^O>{31vZ4CZ8OJu;aSoL@kw%i5VGS|W>N zmEU{QxOA@RbF?`tX+<h($ literal 0 HcmV?d00001 diff --git a/public/js/hls-viewer.js b/public/js/hls-viewer.js index d8e0b4e..b927658 100644 --- a/public/js/hls-viewer.js +++ b/public/js/hls-viewer.js @@ -1,13 +1,12 @@ /* eslint-disable object-shorthand */ (function () { - /** * Options for adding OpenTok publisher and subscriber video elements */ const insertOptions = { width: '100%', height: '100%', - showControls: false + showControls: false, }; /** @@ -40,7 +39,7 @@ const checkBroadcastStatus = function (session) { session.signal({ type: 'broadcast', - data: 'status' + data: 'status', }); }; @@ -75,41 +74,32 @@ let broadcastActive = false; /** Subscribe to new streams as they are published */ - // session.on('streamCreated', function (event) { - // streams.push(event.stream); - // if (broadcastActive) { - // subscribers.push(subscribe(session, event.stream)); - // } - // if (streams.length > 3) { - // document.getElementById('videoContainer').classList.add('wrap'); - // } - // }); - - // session.on('streamDestroyed', function (event) { - // const index = streams.indexOf(event.stream); - // streams.splice(index, 1); - // if (streams.length < 4) { - // document.getElementById('videoContainer').classList.remove('wrap'); - // } - // }); + session.on('streamCreated', function (event) { + console.log(event); + + // if (broadcastActive) { + // subscribers.push(subscribe(session, event.stream)); + // } + // if (streams.length > 3) { + // document.getElementById('videoContainer').classList.add('wrap'); + // } + }); /** Listen for a broadcast status update from the host */ session.on('signal:broadcast-url', function (event) { - console.log("signal:broadcast-url", event); + console.log('signal:broadcast-url', event); const broadcastUrl = event.data; var video = document.getElementById('video'); if (Hls.isSupported()) { var hls = new Hls(); - console.log("signal:broadcast-url - ", broadcastUrl) + console.log('signal:broadcast-url - ', broadcastUrl); hls.loadSource(broadcastUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function () { - playVideo() - + playVideo(); }); - } - else if (video.canPlayType('application/vnd.apple.mpegurl')) { + } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = broadcastUrl; video.addEventListener('canplay', function () { video.play(); @@ -122,41 +112,52 @@ const playVideo = function () { var video = document.getElementById('video'); if (video) { - video.play().then((res) => { - console.log("Play Video Successfull", res); - const playVideoContainer = document.getElementById('play-video-container'); - playVideoContainer.classList.add('hidden'); - }).catch(err => { - console.log("Play Video error", err) - }); + video + .play() + .then((res) => { + console.log('Play Video Successfull', res); + const playVideoContainer = document.getElementById( + 'play-video-container' + ); + playVideoContainer.classList.add('hidden'); + }) + .catch((err) => { + console.log('Play Video error', err); + }); } - } + }; const switchToLiveMode = function () { - window.location.href = 'viewer.html' - } + window.location.href = 'viewer.html'; + }; const addClickEventListeners = function () { document.getElementById('play-video').addEventListener('click', playVideo); - document.getElementById('go-live-btn').addEventListener('click', switchToLiveMode); - } + document + .getElementById('go-live-btn') + .addEventListener('click', switchToLiveMode); + }; const init = function () { addClickEventListeners(); const credentials = getCredentials(); const props = { connectionEventsSuppressed: true }; - const session = OT.initSession(credentials.apiKey, credentials.sessionId, props); + const session = OT.initSession( + credentials.apiKey, + credentials.sessionId, + props + ); setEventListeners(session); session.connect(credentials.token, function (error) { if (error) { console.log(error); } else { + console.log('connected'); + checkBroadcastStatus(session); } }); }; - - document.addEventListener('DOMContentLoaded', init); -}()); +})(); diff --git a/public/js/host.js b/public/js/host.js index 37f5664..636b18e 100644 --- a/public/js/host.js +++ b/public/js/host.js @@ -3,13 +3,13 @@ /* eslint-disable vars-on-top */ (function () { - /** The state of things */ - let broadcast = { status: 'waiting', streams: 1, rtmp: false }; + let broadcast = { status: 'waiting', streams: 2, rtmp: false }; let subscribers = []; let activeSpeaker; let session; let lastActiveSpeaker = Date.now(); + let screenSharePublisher; /** * Options for adding OpenTok publisher and subscriber video elements @@ -17,7 +17,7 @@ const insertOptions = { width: '100%', height: '100%', - showControls: false + showControls: false, }; /** @@ -38,17 +38,49 @@ * Create an OpenTok publisher object */ const initPublisher = function () { - const properties = Object.assign({ name: 'Host', insertMode: 'before' }, insertOptions); + const properties = Object.assign( + { + name: 'Host', + insertMode: 'before', + publishAudio: true, + resolution: check1080pCamera() ? '1920x1080' : '1280x720', + }, + insertOptions + ); const publisher = OT.initPublisher('hostDivider', properties); const subscriberData = { - subscriber: publisher + subscriber: publisher, }; subscribers.push(subscriberData); - publisher.on('audioLevelUpdated', audioLevelUpdate); + // publisher.on('audioLevelUpdated', audioLevelUpdate); return publisher; }; + /** + * Check if you can publish at 1080p + */ + async function check1080pCamera() { + const constraints = { + audio: true, + video: { + width: { min: 1920 }, + height: { min: 1080 }, + }, + }; + let stream; + try { + stream = await navigator.mediaDevices.getUserMedia(constraints); + return true; + } catch (err) { + if (err.name === 'OverconstrainedError') { + alert('⚠️ 1080p is not supported on this device!'); + return false; + } + return true; + } + } + /** * Send the broadcast status to everyone connected to the session using * the OpenTok signaling API @@ -57,10 +89,16 @@ * @param {Object} [to] - An OpenTok connection object */ const signal = function (session, status, to) { - const signalData = Object.assign({}, { type: 'broadcast', data: status }, to ? { to } : {}); + const signalData = Object.assign( + {}, + { type: 'broadcast', data: status }, + to ? { to } : {} + ); session.signal(signalData, function (error) { if (error) { - console.log(['signal error (', error.code, '): ', error.message].join('')); + console.log( + ['signal error (', error.code, '): ', error.message].join('') + ); } else { console.log('signal sent'); } @@ -68,12 +106,23 @@ }; const signalBroadcastURL = function (session, url, to) { - console.log("signalBroadcastURL", to, url); - const signalData = Object.assign({}, { type: 'broadcast-url', data: url }, to ? { to } : {}); - console.log("signalBroadcastURL#1", signalData); + console.log('signalBroadcastURL', to, url); + const signalData = Object.assign( + {}, + { type: 'broadcast-url', data: url }, + to ? { to } : {} + ); + console.log('signalBroadcastURL#1', signalData); session.signal(signalData, function (error) { if (error) { - console.log(['[signalBroadcastURL] - error (', error.code, '): ', error.message].join('')); + console.log( + [ + '[signalBroadcastURL] - error (', + error.code, + '): ', + error.message, + ].join('') + ); } else { console.log('[signalBroadcastURL] - signal sent'); } @@ -86,14 +135,13 @@ * @param {Number} availableAt The time (ms since epoch) at which the stream is available */ const getBroadcastUrl = function (url, availableAt) { - return `${window.location.host}/broadcast?url=${url}&avaialbleAt=${availableAt}`; + return `${window.location.host}/broadcast?url=${url}&availableAt=${availableAt}`; }; /** * Set the state of the broadcast and update the UI */ const updateStatus = function (session, status) { - const startStopButton = document.getElementById('startStop'); const { url, availableAt } = broadcast; const playerUrl = getBroadcastUrl(url, availableAt); @@ -123,13 +171,6 @@ }; // Let the user know that the url has been copied to the clipboard - const showCopiedNotice = function () { - const notice = document.getElementById('copyNotice'); - notice.classList.remove('opacity-0'); - setTimeout(function () { - notice.classList.add('opacity-0'); - }, 1500); - }; const validRtmp = function () { const server = document.getElementById('rtmpServer'); @@ -137,8 +178,10 @@ const serverDefined = !!server.value; const streamDefined = !!stream.value; - const invalidServerMessage = 'The RTMP server url is invalid. Please update the value and try again.'; - const invalidStreamMessage = 'The RTMP stream name must be defined. Please update the value and try again.'; + const invalidServerMessage = + 'The RTMP server url is invalid. Please update the value and try again.'; + const invalidStreamMessage = + 'The RTMP stream name must be defined. Please update the value and try again.'; if (serverDefined && !server.checkValidity()) { document.getElementById('rtmpLabel').classList.add('hidden'); @@ -163,7 +206,13 @@ }; const hideRtmpInput = function () { - ['rtmpLabel', 'rtmpError', 'rtmpServer', 'rtmpStream', 'rtmp-options'].forEach(function (id) { + [ + 'rtmpLabel', + 'rtmpError', + 'rtmpServer', + 'rtmpStream', + 'rtmp-options', + ].forEach(function (id) { document.getElementById(id).classList.add('hidden'); }); }; @@ -172,7 +221,6 @@ * Make a request to the server to start the broadcast */ const startBroadcast = function () { - analytics.log('startBroadcast', 'variationAttempt'); const rtmp = validRtmp(); @@ -185,17 +233,27 @@ const HLS_HD = document.querySelector('#hls-HD').checked; const HLS_DVR = document.querySelector('#hls-dvr').checked; + if (HLS_LL && HLS_DVR) + alert( + 'DVR is not supported with Low latency HLS, DVR will regular HLS will be selected' + ); + hideRtmpInput(); - http.post('/broadcast/start', { - streams: broadcast.streams, rtmp: rtmp, - fhd: HLS_HD, dvr: HLS_DVR, lowLatency: HLS_LL - }) + http + .post('/broadcast/start', { + streams: broadcast.streams, + rtmp: rtmp, + lowLatency: HLS_LL, + fhd: HLS_HD, + dvr: HLS_DVR, + }) .then(function (broadcastData) { broadcast = broadcastData; updateStatus(session, 'active'); signalBroadcastURL(session, broadcast.url, null); analytics.log('startBroadcast', 'variationSuccess'); - }).catch(function (error) { + }) + .catch(function (error) { console.log(error); analytics.log('startBroadcast', 'variationError'); }); @@ -205,7 +263,8 @@ * Make a request to the server to stop the broadcast */ const endBroadcast = function () { - http.post('/broadcast/end') + http + .post('/broadcast/end') .then(function () { updateStatus(session, 'ended'); analytics.log('endBroadcast', 'variationSuccess'); @@ -220,18 +279,26 @@ * Calculates the active speaker amongst all subscribers/publishers */ const calculateActiveSpeaker = function () { - let activeSpeakers = subscribers.filter(f => (f.activity && f.activity.talking)); - activeSpeakers = activeSpeakers.sort((a, b) => a.activity.audioLevel < b.activity.audioLevel); + let activeSpeakers = subscribers.filter( + (f) => f.activity && f.activity.talking + ); + activeSpeakers = activeSpeakers.sort( + (a, b) => a.activity.audioLevel < b.activity.audioLevel + ); const now = Date.now(); if (activeSpeakers.length > 0 && now - lastActiveSpeaker > 1000) { - if (!activeSpeaker || activeSpeaker.subscriber.streamId !== activeSpeakers[0].subscriber.streamId) { + if ( + !activeSpeaker || + activeSpeaker.subscriber.streamId !== + activeSpeakers[0].subscriber.streamId + ) { activeSpeaker = activeSpeakers[0]; lastActiveSpeaker = now; updateClasses(); } } - } + }; /** * Updates the class lists for streams @@ -247,11 +314,12 @@ } return { id: m.subscriber.streamId, - layoutClassList: cssClasses - } + layoutClassList: cssClasses, + }; }); - http.post('/broadcast/classes', { classList: streamClasses }) + http + .post('/broadcast/classes', { classList: streamClasses }) .then(function () { analytics.log('updatedStreamClasses', 'variationSuccess'); }) @@ -259,18 +327,26 @@ console.log(error); analytics.log('updatedStreamClasses', 'variationError'); }); - } + }; /** * Subscribe to a stream */ const subscribe = function (session, stream) { - const properties = Object.assign({ name: 'Guest', insertMode: 'after' }, insertOptions); - const subscriber = session.subscribe(stream, 'hostDivider', properties, function (error) { - if (error) { - console.log(error); + const properties = Object.assign( + { name: 'Guest', insertMode: 'after' }, + insertOptions + ); + const subscriber = session.subscribe( + stream, + 'hostDivider', + properties, + function (error) { + if (error) { + console.log(error); + } } - }); + ); subscribers.push({ subscriber }); subscriber.on('audioLevelUpdated', audioLevelUpdate); @@ -281,16 +357,25 @@ /** * Updates the subscriber with its current audio level - * @param {Object} event AudioLevelUpdatedEvent + * @param {Object} event AudioLevelUpdatedEvent */ const audioLevelUpdate = function (event) { const now = Date.now(); - const subData = subscribers.find(f => f.subscriber.streamId === event.target.streamId); + const subData = subscribers.find( + (f) => f.subscriber.streamId === event.target.streamId + ); if (event.audioLevel > 0.2) { if (!subData.activity) { - subData.activity = { timestamp: now, talking: true, audioLevel: event.audioLevel }; - } else if (subData.activity.talking && now - subData.activity.timestamp > 1000) { + subData.activity = { + timestamp: now, + talking: true, + audioLevel: event.audioLevel, + }; + } else if ( + subData.activity.talking && + now - subData.activity.timestamp > 1000 + ) { subData.activity.timestamp = now; subData.activity.audioLevel = event.audioLevel; } else if (now - subData.activity.timestamp > 1000) { @@ -308,12 +393,14 @@ } subscribers = [ - ...subscribers.filter(f => f.subscriber.streamId !== event.target.streamId), - subData + ...subscribers.filter( + (f) => f.subscriber.streamId !== event.target.streamId + ), + subData, ]; calculateActiveSpeaker(); - } + }; /** * Toggle publishing audio/video to allow host to mute @@ -327,28 +414,71 @@ publisher[el.id](enabled); }; + /** + * Toggle publishing audio/video to allow host to mute + * their video (publishVideo) or audio (publishAudio) + * @param {Object} publisher The OpenTok publisher object + * @param {Object} el The DOM element of the control whose id corresponds to the action + */ + const toggleScreenShare = function (el) { + const enabled = el.classList.contains('disabled'); + el.classList.toggle('disabled'); + + if (!enabled) { + screenSharePublisher = OT.initPublisher( + 'hostDivider', + { videoSource: 'screen', name: 'HostScreen', insertMode: 'before' }, + function (error) { + if (error) { + // Look at error.message to see what went wrong. + } else { + session.publish(screenSharePublisher, function (error) { + if (error) { + // Look error.message to see what went wrong. + } + }); + } + } + ); + + //settig up event listener for when the user clicks on the native browser prompt to stop screen-sharing + + screenSharePublisher.on('mediaStopped', function (e) { + const screenShareButton = document.getElementById('screenshare'); + screenShareButton.classList.toggle('disabled'); + }); + } else { + screenSharePublisher.destroy(); + } + }; + /** * Update broadcast layouts */ const updateBroadcastLayout = function () { if (broadcast.status === 'active') { // streams, type, stylesheet - http.post('/broadcast/layout', { - streams: subscribers.length, - type: subscribers.length > 3 ? 'custom' : 'bestFit' - }) - .then(function (result) { console.log(result); }) - .catch(function (error) { console.log(error); }); + http + .post('/broadcast/layout', { + streams: subscribers.length, + type: subscribers.length > 3 ? 'custom' : 'bestFit', + }) + .then(function (result) { + console.log(result); + }) + .catch(function (error) { + console.log(error); + }); } }; const setEventListeners = function (session, publisher) { - // Add click handler to the start/stop button + const startStopButton = document.getElementById('startStop'); startStopButton.classList.remove('hidden'); startStopButton.addEventListener('click', function () { - if (broadcast.status === 'waiting') { + if (broadcast.status === 'waiting' || broadcast.status === 'ended') { startBroadcast(session); } else if (broadcast.status === 'active') { endBroadcast(session); @@ -360,14 +490,16 @@ subscribe(session, event.stream); if (subscribers.length > 2) { document.getElementById('videoContainer').classList.add('wrap'); - if (broadcast.status === 'active' && subscribers.length <= 3) { - updateBroadcastLayout(); - } + // if (broadcast.status === 'active' && subscribers.length <= 3) { + // updateBroadcastLayout(); + // } } }); session.on('streamDestroyed', function (event) { - subscribers = subscribers.filter(f => f.subscriber.streamId !== event.stream.id); + subscribers = subscribers.filter( + (f) => f.subscriber.streamId !== event.stream.id + ); if (subscribers.length <= 3) { document.getElementById('videoContainer').classList.remove('wrap'); if (broadcast.status === 'active' && subscribers.length < 3) { @@ -378,18 +510,12 @@ // Signal the status of the broadcast when requested session.on('signal:broadcast', function (event) { + console.log('pinging broadcast status'); + if (event.data === 'status') { signal(session, broadcast.status, event.from); - } - }); - - session.on('connectionCreated', function (event) { - console.log("connectionCreated", event); - console.log("session", session); - if (event.connection.connectionId !== session.connection.id) { - // send signal to new connected users if (broadcast.status === 'active') { - signalBroadcastURL(session, broadcast.url, event.connection.connectionId); + signalBroadcastURL(session, broadcast.url, event.from); } } }); @@ -398,27 +524,80 @@ showCopiedNotice(); }); - document.getElementById('publishVideo').addEventListener('click', function () { - toggleMedia(publisher, this); + document.getElementById('videoInputs').addEventListener('change', (e) => { + onVideoSourceChanged(e, publisher); }); - document.getElementById('publishAudio').addEventListener('click', function () { - toggleMedia(publisher, this); + document.getElementById('audioInputs').addEventListener('change', (e) => { + onAudioSourceChanged(e, publisher); + }); + + navigator.mediaDevices.ondevicechange = () => { + refreshDeviceList(publisher); + }; + + publisher.on('accessAllowed', () => { + refreshDeviceList(publisher); }); + document + .getElementById('publishVideo') + .addEventListener('click', function () { + toggleMedia(publisher, this); + }); + + document + .getElementById('publishAudio') + .addEventListener('click', function () { + toggleMedia(publisher, this); + }); + + document + .getElementById('screenshare') + .addEventListener('click', function () { + toggleScreenShare(this); + }); }; const addPublisherControls = function (publisher) { const publisherContainer = document.getElementById(publisher.element.id); const el = document.createElement('div'); + // const controls = [ + // '
', + // '
', + // '
', + // '
', + const controls = [ + '
', + '
', '
', '
', + + '', + '
', + '
', + + '
', + '
', '
', + '', + '
', + '
', + + '
', + '
', + '
', + '
', ].join('\n'); el.innerHTML = controls; publisherContainer.appendChild(el.firstChild); + refreshDeviceList(publisher); }; /** @@ -433,6 +612,143 @@ setEventListeners(session, publisher); }; + const refreshDeviceList = (pub) => { + console.log('refreshDeviceList'); + listVideoInputs().then((devices) => { + console.log(devices); + const videoSelect = document.getElementById('videoInputs'); + videoSelect.innerHTML = ''; + console.log(pub); + + const currentVideoSource = pub.getVideoSource(); + + // Select Input + const currentVideoOption = document.createElement('option'); + //disabledOption.disabled = true; + currentVideoOption.innerText = currentVideoSource.track.label; + currentVideoOption.classList.add('dropdown-item'); + currentVideoOption.value = currentVideoSource.track.label; + currentVideoOption.selected = true; + videoSelect.appendChild(currentVideoOption); + + for (let i = 0; i < devices.length; i += 1) { + if (devices[i].deviceId != currentVideoSource.deviceId) { + const deviceOption = document.createElement('option'); + deviceOption.classList.add('dropdown-item'); + deviceOption.innerText = devices[i].label || `Video Input ${i + 1}`; + // deviceOption.value = `video-source-${i}`; + deviceOption.value = devices[i].label; + + videoSelect.appendChild(deviceOption); + } + } + + if (devices.length === 0) { + const deviceOption = document.createElement('option'); + deviceOption.innerText = 'Default Video Input'; + deviceOption.value = `default-video`; + + videoSelect.appendChild(deviceOption); + } + }); + + listAudioInputs().then((devices) => { + const audioSelect = document.getElementById('audioInputs'); + audioSelect.innerHTML = ''; + const currentAudioSource = pub.getAudioSource(); + console.log(devices); + + // Select Input + const currentAudioOption = document.createElement('option'); + currentAudioOption.innerText = currentAudioSource.label; + + currentAudioOption.value = currentAudioSource.label; + currentAudioOption.selected = true; + audioSelect.appendChild(currentAudioOption); + + for (let i = 0; i < devices.length; i += 1) { + if (devices[i].label != currentAudioSource.label) { + const deviceOption = document.createElement('option'); + deviceOption.innerText = devices[i].label || `Audio Input ${i + 1}`; + deviceOption.value = devices[i].label; + + audioSelect.appendChild(deviceOption); + } + } + + if (devices.length === 0) { + const deviceOption = document.createElement('option'); + deviceOption.innerText = 'Default Audio Input'; + deviceOption.value = `default-audio`; + + audioSelect.appendChild(deviceOption); + } + }); + }; + + const onVideoSourceChanged = async (event, publisher) => { + console.log(event); + const labelToFind = event.target.value; + const videoDevices = await listVideoInputs(); + + const deviceId = videoDevices.find( + (e) => e.label === labelToFind + )?.deviceId; + + if (deviceId != null) { + publisher.setVideoSource(deviceId); + } + }; + + const onAudioSourceChanged = async (event, publisher) => { + const labelToFind = event.target.value; + const audioDevices = await listAudioInputs(); + + const deviceId = audioDevices.find( + (e) => e.label === labelToFind + )?.deviceId; + + if (deviceId != null) { + publisher.setAudioSource(deviceId); + } + }; + + const listAudioInputs = async () => { + try { + const devices = await listDevices(); + const filteredDevices = devices.filter( + (device) => device.kind === 'audioInput' + ); + return Promise.resolve(filteredDevices); + } catch (error) { + return Promise.reject(error); + } + }; + + const listVideoInputs = async () => { + try { + const devices = await listDevices(); + const filteredDevices = devices.filter( + (device) => device.kind === 'videoInput' + ); + return Promise.resolve(filteredDevices); + } catch (error) { + return Promise.reject(error); + } + }; + + const listDevices = () => { + return new Promise((resolve, reject) => { + OT.getDevices((error, devices) => { + if (error) { + reject(error); + } else { + resolve(devices); + } + }); + }); + }; + const init = function () { const clipboard = new Clipboard('#copyURL'); // eslint-disable-line no-unused-vars const credentials = getCredentials(); @@ -456,4 +772,4 @@ }; document.addEventListener('DOMContentLoaded', init); -}()); +})(); diff --git a/public/js/viewer.js b/public/js/viewer.js index fa387f3..7373610 100644 --- a/public/js/viewer.js +++ b/public/js/viewer.js @@ -1,13 +1,12 @@ /* eslint-disable object-shorthand */ (function () { - /** * Options for adding OpenTok publisher and subscriber video elements */ const insertOptions = { width: '100%', height: '100%', - showControls: false + showControls: false, }; /** @@ -28,19 +27,27 @@ const subscribe = function (session, stream) { const name = stream.name; const insertMode = name === 'Host' ? 'before' : 'after'; - const properties = Object.assign({ name: name, insertMode: insertMode }, insertOptions); - return session.subscribe(stream, 'hostDivider', properties, function (error) { - if (error) { - console.log(error); + const properties = Object.assign( + { name: name, insertMode: insertMode }, + insertOptions + ); + return session.subscribe( + stream, + 'hostDivider', + properties, + function (error) { + if (error) { + console.log(error); + } } - }); + ); }; /** Ping the host to see if the broadcast has started */ const checkBroadcastStatus = function (session) { session.signal({ type: 'broadcast', - data: 'status' + data: 'status', }); }; @@ -93,6 +100,7 @@ broadcastActive = status === 'active'; if (status === 'active') { + document.getElementById('back-hls').classList.remove('hidden'); streams.forEach(function (stream) { subscribers.push(subscribe(session, stream)); }); @@ -105,10 +113,27 @@ }); }; + const switchToHlsMode = function (e) { + console.log(window.location.href); + + window.location.href = 'hls-viewer'; + }; + const addClickEventListeners = function () { + document + .getElementById('go-hls-btn') + .addEventListener('click', switchToHlsMode); + }; + const init = function () { + addClickEventListeners(); + const credentials = getCredentials(); const props = { connectionEventsSuppressed: true }; - const session = OT.initSession(credentials.apiKey, credentials.sessionId, props); + const session = OT.initSession( + credentials.apiKey, + credentials.sessionId, + props + ); session.connect(credentials.token, function (error) { if (error) { @@ -121,4 +146,4 @@ }; document.addEventListener('DOMContentLoaded', init); -}()); +})(); diff --git a/services/opentok-api.js b/services/opentok-api.js index dd8b618..c2d5b7e 100644 --- a/services/opentok-api.js +++ b/services/opentok-api.js @@ -89,25 +89,28 @@ const tokenOptions = (userType) => { const createSession = async (options) => { return new Promise((resolve, reject) => { try { - OT.createSession({ ...defaultSessionOptions, ...options }, (err, session) => { - if (err) resolve(err); + OT.createSession( + { ...defaultSessionOptions, ...options }, + (err, session) => { + if (err) resolve(err); - activeSession = session; - resolve(session); - }); - } - catch (err) { + activeSession = session; + resolve(session); + } + ); + } catch (err) { reject(err); } }); -} +}; /** * Create an OpenTok token * @param {String} userType Host, guest, or viewer * @returns {String} */ -const createToken = userType => OT.generateToken(activeSession.sessionId, tokenOptions(userType)); +const createToken = (userType) => + OT.generateToken(activeSession.sessionId, tokenOptions(userType)); /** * Creates an OpenTok session and generates an associated token @@ -118,15 +121,14 @@ const getCredentials = async (userType) => { if (!activeSession) { try { await createSession(); - } - catch (err) { + } catch (err) { reject(err); } } const token = createToken(userType); resolve({ apiKey, sessionId: activeSession.sessionId, token }); }); -} +}; /** * Start the broadcast and keep the active broadcast in memory @@ -134,30 +136,40 @@ const getCredentials = async (userType) => { * @param {String} [rmtp] - The (optional) RTMP stream url * @returns {Promise} {Object} Broadcast data, Reject => {Error}> */ -const startBroadcast = async (streams, rmtp, fhd = false, dvr = false, lowLatency = false) => { +const startBroadcast = async ( + streams, + rmtp, + lowLatency, + fhd = false, + dvr = false +) => { return new Promise((resolve, reject) => { - console.log("StartBroadcast API"); - let layout; + console.log('StartBroadcast API'); + console.log('low latency ' + lowLatency); + + const layout = { + type: 'bestFit', + screenshareType: 'pip', + }; let dvrConfig = dvr; let lowLatencyConfig = lowLatency; - if (streams > 3) { - layout = { - type: 'bestFit' - }; - } - else { - layout = { - type: 'custom', - stylesheet: customStyle, - }; - } + // if (streams > 3) { + // layout = { + // type: 'bestFit', + // }; + // } else { + // layout = { + // type: 'custom', + // stylesheet: customStyle, + // }; + // } if (dvrConfig) { lowLatencyConfig = false; // DVR and LL are not compatible } const outputs = { hls: { dvr: dvrConfig, - lowLatency: lowLatencyConfig + lowLatency: lowLatencyConfig, }, }; const sessionId = activeSession.sessionId; @@ -168,31 +180,36 @@ const startBroadcast = async (streams, rmtp, fhd = false, dvr = false, lowLatenc outputs.rmtp = rmtp; } - const resolution = fhd ? "1920x1080" : process.env.broadcastDefaultResolution ? process.env.broadcastDefaultResolution : "1280x720"; - - + const resolution = fhd + ? '1920x1080' + : process.env.broadcastDefaultResolution + ? process.env.broadcastDefaultResolution + : '1280x720'; try { - OT.startBroadcast(sessionId, { layout, outputs, resolution }, function (err, broadcast) { - if (err) reject(err); + OT.startBroadcast( + sessionId, + { layout, outputs, resolution }, + function (err, broadcast) { + if (err) reject(err); - activeBroadcast = { - id: broadcast.id, - session: broadcast.sessionId, - rmtp: broadcast.broadcastUrls.rmtp, - url: broadcast.broadcastUrls.hls, - apiKey: apiKey, - availableAt: broadcast.createdAt + broadcastDelay - }; - console.log("activeBroadcast", activeBroadcast) - resolve(activeBroadcast); - }); - } - catch (err) { + activeBroadcast = { + id: broadcast.id, + session: broadcast.sessionId, + rmtp: broadcast.broadcastUrls.rmtp, + url: broadcast.broadcastUrls.hls, + apiKey: apiKey, + availableAt: broadcast.createdAt + broadcastDelay, + }; + console.log('activeBroadcast', activeBroadcast); + resolve(activeBroadcast); + } + ); + } catch (err) { reject(err); } }); -} +}; /** * End the broadcast @@ -200,7 +217,7 @@ const startBroadcast = async (streams, rmtp, fhd = false, dvr = false, lowLatenc */ const stopBroadcast = async () => { return new Promise((resolve, reject) => { - console.log("StopBroadcast API"); + console.log('StopBroadcast API'); if (!activeBroadcast) { reject({ error: 'No active broadcast session found' }); } @@ -210,15 +227,13 @@ const stopBroadcast = async () => { if (err) reject(err); resolve(broadcast); }); - } - catch (err) { + } catch (err) { reject(err); - } - finally { + } finally { activeBroadcast = null; } }); -} +}; /** * Dynamically update the broadcast layout @@ -236,27 +251,29 @@ const updateLayout = async (streams, type) => { if (!type) { if (streams > 3) { type = 'bestFit'; - } - else { + } else { type = 'custom'; stylesheet = customStyle; } - } - else if (type === 'custom') { + } else if (type === 'custom') { stylesheet = customStyle; } try { - OT.setBroadcastLayout(activeBroadcast.id, type, stylesheet, function (err) { - if (err) reject(err); - resolve(); - }); - } - catch (err) { + OT.setBroadcastLayout( + activeBroadcast.id, + type, + stylesheet, + function (err) { + if (err) reject(err); + resolve(); + } + ); + } catch (err) { reject(err); } }); -} +}; /** * Dynamically update class lists for streams @@ -273,21 +290,24 @@ const updateLayout = async (streams, type) => { const updateStreamClassList = async (classListArray) => { return new Promise((resolve, reject) => { try { - OT.setStreamClassLists(activeSession.sessionId, classListArray, function (err) { - if (err) reject(err); - resolve(); - }); - } - catch (err) { + OT.setStreamClassLists( + activeSession.sessionId, + classListArray, + function (err) { + if (err) reject(err); + resolve(); + } + ); + } catch (err) { reject(err); } }); -} +}; module.exports = { getCredentials, startBroadcast, stopBroadcast, updateLayout, - updateStreamClassList -}; \ No newline at end of file + updateStreamClassList, +}; diff --git a/views/pages/hls-viewer.ejs b/views/pages/hls-viewer.ejs index dd15a37..6535001 100644 --- a/views/pages/hls-viewer.ejs +++ b/views/pages/hls-viewer.ejs @@ -1,51 +1,67 @@ - - - <%- include('../partials/head') %> + + <%- include('../partials/head') %> - + - - -
- - -