diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 5199876..2ca79d9 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,6 +1,6 @@ # Dimension Development -Dimension is split into two layers: the frontend (web) and backend. The frontend is responsible for interacting with the client (Riot) directly and hands off any complex work to the backend for processing. +Dimension is split into two layers: the frontend (web) and backend. The frontend is responsible for interacting with the client (Element) directly and hands off any complex work to the backend for processing. **For help and support related to Dimension development, please visit:** [![#dimension:t2bot.io](https://img.shields.io/badge/matrix-%23dimension:t2bot.io-brightgreen.svg)](https://matrix.to/#/#dimension:t2bot.io) @@ -33,7 +33,7 @@ Integrations are defined into one of four categories: * Bridges - Application services that bridge the room in some way to an external network (IRC, Webhooks, etc) * Widgets - Added functionality through iframes for rooms/users -The backend further breaks these categories out to redirect traffic to the correct place. For instance, the admin backend +The backend further breaks these categories out to redirect traffic to the correct place. For instance, the admin backend breaks out go-neb specifically as it's configuration is fairly involved. The backend has 3 major layers: @@ -41,13 +41,13 @@ The backend has 3 major layers: * The data stores (where requests normally get routed to) * The proxy (where we flip between using upstream configurations and self-hosted) -Many of the API routes are generic, however many of the integrations require additional structure that the routes cannot +Many of the API routes are generic, however many of the integrations require additional structure that the routes cannot provide. For example, the IRC bridge is complicated in that it needs a dedicated API in order to be configured, however the bots can work well within their constraints. ## Frontend Architecture -The frontend app is split into two major parts: The Riot frontend and the admin section. The components are nested under +The frontend app is split into two major parts: The Element frontend and the admin section. The components are nested under their respective categories and route. For example, the edit page for the Jitsi widget is under the Widgets directory. The frontend is otherwise a fairly basic Angular 5 application: there's components, services, etc. The services should be diff --git a/Dockerfile b/Dockerfile index 9ae7b47..161371e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,47 +1,44 @@ -FROM node:10.16.0-alpine +FROM node:12.16.1-alpine AS builder LABEL maintainer="Andreas Peters " #Upstream URL: https://git.aventer.biz/AVENTER/docker-matrix-dimension -RUN apk add dos2unix --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/community/ --allow-untrusted +WORKDIR /home/node/matrix-dimension -RUN apk update && \ - apk add --no-cache bash gcc python make g++ sqlite && \ - mkdir /home/node/.npm-global && \ - mkdir -p /home/node/app +RUN mkdir -p /home/node/matrix-dimension -COPY ./docker-entrypoint.sh / COPY . /home/node/matrix-dimension - -RUN chown -R node:node /home/node/app && \ - chown -R node:node /home/node/.npm-global && \ - chown -R node:node /home/node/matrix-dimension +RUN chown -R node /home/node/matrix-dimension USER node -ENV PATH=/home/node/.npm-global/bin:$PATH -ENV NPM_CONFIG_PREFIX=/home/node/.npm-global +RUN npm clean-install && \ + node /home/node/matrix-dimension/scripts/convert-newlines.js /home/node/matrix-dimension/docker-entrypoint.sh && \ + NODE_ENV=production npm run-script build -RUN cd /home/node/matrix-dimension && \ - npm install -D wd rimraf webpack webpack-command sqlite3 && \ - NODE_ENV=production npm run-script build:web && npm run-script build:app +FROM node:12.16.1-alpine -USER root +WORKDIR /home/node/matrix-dimension -RUN apk del gcc make g++ && \ - rm /home/node/matrix-dimension/Dockerfile && \ - rm /home/node/matrix-dimension/docker-entrypoint.sh && \ - dos2unix /docker-entrypoint.sh +COPY --from=builder /home/node/matrix-dimension/docker-entrypoint.sh / + +COPY --from=builder /home/node/matrix-dimension/build /home/node/matrix-dimension/build +COPY --from=builder /home/node/matrix-dimension/package* /home/node/matrix-dimension/ +COPY --from=builder /home/node/matrix-dimension/config /home/node/matrix-dimension/config + +RUN chown -R node /home/node/matrix-dimension USER node +RUN npm clean-install --production + VOLUME ["/data"] # Ensure the database doesn't get lost to the container ENV DIMENSION_DB_PATH=/data/dimension.db EXPOSE 8184 -#CMD ["/bin/sh"] +# CMD ["/bin/sh"] ENTRYPOINT ["/docker-entrypoint.sh"] - + diff --git a/README.md b/README.md index 91046f4..94ee7ac 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![TravisCI badge](https://travis-ci.org/turt2live/matrix-dimension.svg?branch=master)](https://travis-ci.org/turt2live/matrix-dimension) -An open source integration manager for matrix clients, like Riot. For help and support, please visit +An open source integration manager for matrix clients, like Element. For help and support, please visit us in [#dimension:t2bot.io](https://matrix.to/#/#dimension:t2bot.io) on Matrix. # Installing Dimension / Running your own @@ -22,13 +22,13 @@ port. Dimension will use the first record it sees and will only communicate over 3. **Verify the homeserver information in your configuration.** The name, access token, and client/ server API URL all need to be set to point towards your homeserver. It may also be necessary to set the federation URL if you're running a private server. -4. **Run the troubleshooter.** If you're on Riot 1.1.0 or higher, type `/addwidget https://dimension.t2bot.io/widgets/manager-test` +4. **Run the troubleshooter.** If you're on Element, type `/addwidget https://dimension.t2bot.io/widgets/manager-test` in a private room then click the button. # Do I need an integrations manager? Integration managers aim to ease a user's interaction with the various services a homeserver may -provide. Often times the integrations manager provided by Riot.im, named Modular, is more than suitable. +provide. Often times the integrations manager provided by Element, is more than suitable. However, there are a few cases where running your own makes more sense: * Wanting to self-host all aspects of your services (client, homeserver, and integrations) diff --git a/config/default.yaml b/config/default.yaml index 3b845c9..e77735d 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -30,7 +30,7 @@ homeserver: accessToken: "something" # These users can modify the integrations this Dimension supports. -# To access the admin interface, open Dimension in Riot and click the settings icon. +# To access the admin interface, open Dimension in Element and click the settings icon. admins: - "@someone:domain.com" @@ -99,4 +99,4 @@ logging: fileLevel: verbose rotate: size: 52428800 # bytes, default is 50mb - count: 5 \ No newline at end of file + count: 5 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index ef8194b..46a3ba5 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,7 +1,4 @@ -#!/bin/bash -set -e - -cd /home/node/matrix-dimension/ +#!/bin/sh if [ -f "/data/config.yaml" ]; then cp /data/config.yaml /home/node/matrix-dimension/config/production.yaml diff --git a/docs/installing.md b/docs/installing.md index cb0a549..6ae75da 100644 --- a/docs/installing.md +++ b/docs/installing.md @@ -1,22 +1,22 @@ ## Installing Dimension -**Note**: Dimension is currently only capable of running with Riot Web or Desktop. The iOS and Android -apps are not directly supported without compiling your own versions. In future, this should be handled -by [an integration manager specification](https://github.com/turt2live/matrix-dimension/issues/262). +**Note**: Dimension is only supported in Element Web and Desktop at the moment. With some effort, +it can be used in other clients or Element iOS/Android, though is not guaranteed to work. In future, +this should be handled by [an integration manager specification](https://github.com/turt2live/matrix-dimension/issues/262). -There are several options for installing Dimension. The easiest is dependent on how you have Riot +There are several options for installing Dimension. The easiest is dependent on how you have Element and your homeserver set up. If you're using [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy), there are already options for configuring Dimension. ### Step 0: Requirements You will need a functioning homeserver (such as [Synapse](https://github.com/matrix-org/synapse)) and -a client to access Dimension with. Currently, that means using [Riot Web or Desktop](https://riot.im). +a client to access Dimension with. Currently, that means using [Element Web or Desktop](https://element.io). Additionally, you will need to be able to host Dimension on a dedicated domain. If your homeserver is set up for example.org, we recommend using dimension.example.org for Dimension. -Finally, this guide assumes you are running nginx as a webserver for Riot, your homeserver, and +Finally, this guide assumes you are running nginx as a webserver for Element, your homeserver, and Dimension. A basic configuration before setting up Dimension would be: ```conf @@ -34,7 +34,7 @@ server { } server { - # Simple configuration for serving Riot + # Simple configuration for serving Element server_name chat.example.org; listen 443 ssl; listen [::]:443 ssl; @@ -62,7 +62,7 @@ it yourself. If you're using Docker, create a directory at `/etc/dimension` (or wherever you'd like - just remember where it is!). -To build Dimension yourself, you'll need Node 10, npm 6, and 2-4gb of RAM. The following steps are enough +To build Dimension yourself, you'll need Node 10+, npm 6+, and 2-4gb of RAM. The following steps are enough to get you started: ```bash # Download dimension @@ -134,11 +134,11 @@ Reload or restart nginx after creating the configuration. ### Step 5: Final steps If everything went according to plan, you should be able to visit `https://dimension.example.org` -and see instructions for configuring Riot. If you don't, your configuration isn't working as +and see instructions for configuring Element. If you don't, your configuration isn't working as intended - double check that all the configuration is set up and visit [#dimension:t2bot.io](https://matrix.to/#/#dimension:t2bot.io) for further help. -After configuring Riot, click the integrations button (4 squares in the top right of any room) and +After configuring Element, click the integrations button (4 squares in the top right of any room) and then click the gear icon. If you don't see a gear icon, you're not an admin in the config. This is where you'll configure different integrations as Dimension doesn't ship with anything enabled by default - click around and start enabling things. diff --git a/docs/notes/riot_widgets.md b/docs/notes/element_widgets.md similarity index 77% rename from docs/notes/riot_widgets.md rename to docs/notes/element_widgets.md index 4410f0d..42ab4f1 100644 --- a/docs/notes/riot_widgets.md +++ b/docs/notes/element_widgets.md @@ -1,6 +1,6 @@ -# Riot Widgets +# Element Widgets -Riot uses some special interaction with the integration manager to make for a clean user experience. +Element uses some special interaction with the integration manager to make for a clean user experience. ### Edit Widget button @@ -19,4 +19,4 @@ Ends up calling `$scalar_ui_url?integ_id=...&screen=...` alongside the standard * Creators of widgets do not get prompted for permission to load * Only people with permission to add widgets can remove widgets (otherwise it just revokes permission) -* Only creators of widgets can edit them \ No newline at end of file +* Only creators of widgets can edit them diff --git a/docs/reference/riot_widget_api.md b/docs/reference/element_widget_api.md similarity index 78% rename from docs/reference/riot_widget_api.md rename to docs/reference/element_widget_api.md index 0092ae3..dccf15c 100644 --- a/docs/reference/riot_widget_api.md +++ b/docs/reference/element_widget_api.md @@ -1,12 +1,12 @@ -# Riot's Widget API +# Element's Widget API -Widgets and Riot communicate using cross-origin messages in a defined format (described in this document). Widgets have access to the entire Scalar Client API, but generally do not need any of the endpoints there. Riot provides additional APIs available to particular widgets for which the integrations manager can not access. The full source for the widget messaging layer in Riot can be seen [here](https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/WidgetMessaging.js). The API is restricted to ensure rogue widgets cannot take over the Riot instance. +Widgets and Element communicate using cross-origin messages in a defined format (described in this document). Widgets have access to the entire Scalar Client API, but generally do not need any of the endpoints there. Element provides additional APIs available to particular widgets for which the integrations manager can not access. The full source for the widget messaging layer in Element can be seen [here](https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/WidgetMessaging.js). The API is restricted to ensure rogue widgets cannot take over the Element instance. **Note**: This is largely out of date and better documented in the Matrix spec nowadays. See https://github.com/matrix-org/matrix-doc/issues/1236 for more information. ## Setting up communications -Riot will automatically open a channel for receiving messages. The widget needs to do the same so it can speak to Riot. Here's some sample JavaScript that will do this for us: +Element will automatically open a channel for receiving messages. The widget needs to do the same so it can speak to Element. Here's some sample JavaScript that will do this for us: ``` window.addEventListener("message", function(event) { @@ -18,7 +18,7 @@ window.addEventListener("message", function(event) { function sendMessage(action, widgetId, otherFields) { if (!otherFields) otherFields = {}; - + var request = otherFields; request["widgetId"] = widgetId; request["action"] = action; @@ -64,7 +64,7 @@ An error response will always have the following structure under `response`: ### Versions / Changelog -All versions use a semantic versioning scheme. The actions recorded in this document include which version they were implemented in. The changelog here is for convience. +All versions use a semantic versioning scheme. The actions recorded in this document include which version they were implemented in. The changelog here is for convience. **v0.0.1** * Initial release diff --git a/docs/reference/scalar_auth.md b/docs/reference/scalar_auth.md index eadc622..ddff98d 100644 --- a/docs/reference/scalar_auth.md +++ b/docs/reference/scalar_auth.md @@ -1,13 +1,13 @@ # Scalar Authentication / Registration -When the "Manage Integrations" button is first clicked by a user, Riot will try and register with the Integrations Manager +When the "Manage Integrations" button is first clicked by a user, Element will try and register with the Integrations Manager to get a `scalar_token` that it then uses to authenticate all future requests with the manager. ## `$restUrl/register` -This ends up mapping to `/api/v1/scalar/register` when Dimension is correctly set up for a Riot instance. +This ends up mapping to `/api/v1/scalar/register` when Dimension is correctly set up for a Element instance. -Riot will POST to this endpoint an OpenID object that looks similar to the following: +Element will POST to this endpoint an OpenID object that looks similar to the following: ``` { "access_token": "ABCDEFGH", @@ -17,7 +17,7 @@ Riot will POST to this endpoint an OpenID object that looks similar to the follo } ``` -`expires_in` is given in seconds. +`expires_in` is given in seconds. With this information, we can hit the federation API on the `matrix_server_name` to get ourselves the Matrix User ID (MXID) of the user. This is a GET request to `http://matrix.org/_matrix/federation/v1/openid/userinfo?access_token=ABCDEFGH`. @@ -39,4 +39,4 @@ following JSON is more than enough: } ``` -Riot will now use this token in future requests by hitting the `"integrations_ui_url"` with `?access_token=some_generated_string`. \ No newline at end of file +Element will now use this token in future requests by hitting the `"integrations_ui_url"` with `?access_token=some_generated_string`. diff --git a/docs/reference/scalar_client_api.md b/docs/reference/scalar_client_api.md index df9cf2c..db113c3 100644 --- a/docs/reference/scalar_client_api.md +++ b/docs/reference/scalar_client_api.md @@ -1,10 +1,10 @@ -# Scalar API (Riot) +# Scalar API (Element) -Scalar and Riot communicate using cross-origin messages in a defined format (described in this document). The full source for the messaging layer in Riot can be seen [here](https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/ScalarMessaging.js). With this API, the integrations manager is able to invite users, get some basic state information, and interact with the room in a limited capacity. The API is intentionally restricted to ensure that misbehaving domains don't have full control over Riot. +Scalar and Element communicate using cross-origin messages in a defined format (described in this document). The full source for the messaging layer in Element can be seen [here](https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/ScalarMessaging.js). With this API, the integrations manager is able to invite users, get some basic state information, and interact with the room in a limited capacity. The API is intentionally restricted to ensure that misbehaving domains don't have full control over Element. ## Setting up communications -Riot will automatically open a channel for receiving messages. The integrations manager needs to do the same so it can speak to Riot. Here's some sample JavaScript that will do this for us: +Element will automatically open a channel for receiving messages. The integrations manager needs to do the same so it can speak to Element. Here's some sample JavaScript that will do this for us: ``` window.addEventListener("message", function(event) { @@ -16,7 +16,7 @@ window.addEventListener("message", function(event) { function sendMessage(action, roomId, userId, otherFields) { if (!otherFields) otherFields = {}; - + var request = otherFields; request["user_id"] = userId; request["room_id"] = roomId; @@ -393,7 +393,7 @@ sendMessage("set_widget", "!curbf:matrix.org", null, { ``` *Note*: Widgets are documented by the matrix.org team [on this Google Doc](https://docs.google.com/document/d/1TiWNDcEOULeRYQpkJHQDjgIW32ohIJSi5MKv9oRdzCo/edit). That document is the source of truth for the event structure and usage. -*Note*: `scalar_token` will be appended to the query string if the widget's url matches the API URL of the integration manager (in Riot) +*Note*: `scalar_token` will be appended to the query string if the widget's url matches the API URL of the integration manager (in Element) ### Getting the room's encryption status @@ -432,4 +432,4 @@ sendMessage("close_scalar"); "action": "close_scalar", "response": null } -``` \ No newline at end of file +``` diff --git a/docs/reference/scalar_server_api.md b/docs/reference/scalar_server_api.md index 5ecf0f0..2c73b6a 100644 --- a/docs/reference/scalar_server_api.md +++ b/docs/reference/scalar_server_api.md @@ -709,25 +709,9 @@ None of these are officially documented, and are subject to change. "authenticated": true, "session": { "Repos": [ - { - "name": "riot-welcome-page", - "description": "A welcome page specific for tang.ents.ca (built for Riot)", - "private": false, - "html_url": "https:\/\/github.com\/ENTS-Source\/riot-welcome-page", - "created_at": "2017-06-10T16:54:37Z", - "updated_at": "2017-06-10T19:10:21Z", - "pushed_at": "2017-06-10T18:15:07Z", - "fork": false, - "full_name": "ENTS-Source\/riot-welcome-page", - "permissions": { - "admin": true, - "pull": true, - "push": true - } - }, { "name": "matrix-dimension", - "description": "An alternative integrations manager for Riot", + "description": "An alternative integrations manager for Element", "private": false, "html_url": "https:\/\/github.com\/turt2live\/matrix-dimension", "created_at": "2017-05-25T21:41:55Z", @@ -818,4 +802,4 @@ None of these are officially documented, and are subject to change. }, "cached_response": false } -``` \ No newline at end of file +``` diff --git a/package-lock.json b/package-lock.json index 6f6f075..8119870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1319,9 +1319,9 @@ } }, "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, "acorn-dynamic-import": { @@ -1869,44 +1869,34 @@ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" }, "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" } } }, @@ -2118,25 +2108,6 @@ "isarray": "^1.0.0" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2435,14 +2406,59 @@ } }, "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "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 + }, + "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 + }, + "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, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "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, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } } }, "clone": { @@ -3331,11 +3347,11 @@ "dev": true }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "^2.0.0" } }, "deep-equal": { @@ -3622,18 +3638,18 @@ } }, "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", "dev": true, "requires": { "is-obj": "^1.0.0" } }, "dottie": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", - "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", + "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, "duplexify": { "version": "3.7.1", @@ -3694,9 +3710,9 @@ "dev": true }, "elliptic": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", - "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -3912,12 +3928,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "dev": true - }, "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", @@ -4228,11 +4238,6 @@ "schema-utils": "^1.0.0" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -4471,11 +4476,6 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "fs-copy-file-sync": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz", - "integrity": "sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ==" - }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -4557,7 +4557,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4578,12 +4579,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4598,17 +4601,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4725,11 +4731,6 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, "dev": true, "optional": true }, @@ -4737,6 +4738,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4751,6 +4753,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4758,12 +4761,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4782,6 +4787,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4862,7 +4868,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4874,6 +4881,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4959,7 +4967,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4995,6 +5004,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5014,6 +5024,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5057,12 +5068,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -5265,9 +5278,9 @@ } }, "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", "dev": true, "requires": { "glob": "~7.1.1", @@ -5427,9 +5440,9 @@ } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "hpack.js": { @@ -5614,14 +5627,22 @@ "dev": true }, "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { - "eventemitter3": "^3.0.0", + "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + } } }, "http-proxy-middleware": { @@ -5683,8 +5704,7 @@ "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "iferr": { "version": "0.1.5", @@ -5751,9 +5771,9 @@ "dev": true }, "in-publish": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", - "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", + "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==", "dev": true }, "indent-string": { @@ -5791,9 +5811,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" }, "internal-ip": { "version": "4.3.0", @@ -5819,12 +5839,6 @@ "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -5976,13 +5990,10 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -6172,15 +6183,15 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jquery": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", - "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", + "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==", "dev": true }, "js-base64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, "js-levenshtein": { @@ -6286,15 +6297,6 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -6353,9 +6355,9 @@ } }, "lodash": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.13.tgz", - "integrity": "sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -6396,9 +6398,9 @@ "dev": true }, "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==" + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, "lodash.tail": { "version": "4.1.1", @@ -6770,9 +6772,9 @@ "dev": true }, "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" }, "mini-css-extract-plugin": { "version": "0.7.0", @@ -6930,6 +6932,11 @@ } } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "module-parent": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/module-parent/-/module-parent-0.0.2.tgz", @@ -6941,9 +6948,9 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moment-timezone": { - "version": "0.5.27", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz", - "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==", + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", + "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", "requires": { "moment": ">= 2.9.0" } @@ -7035,9 +7042,9 @@ } }, "napi-build-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz", - "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, "needle": { "version": "2.4.0", @@ -7117,20 +7124,25 @@ } }, "node-abi": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.9.0.tgz", - "integrity": "sha512-jmEOvv0eanWjhX8dX1pmjb7oJl1U1oR4FOh0b2GnvALwSYoOdU7sj+kLDSAyjo4pfC9aj/IxkloxdLJQhSSQBA==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz", + "integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==", "requires": { "semver": "^5.4.1" }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, + "node-addon-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", + "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -7306,9 +7318,9 @@ } }, "node-sass": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", - "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -7318,14 +7330,14 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.15", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.13.2", "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", - "sass-graph": "^2.2.4", + "sass-graph": "2.2.5", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, @@ -7349,6 +7361,24 @@ "supports-color": "^2.0.0" } }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "sass-graph": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^13.3.2" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -7392,9 +7422,9 @@ }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } @@ -7632,15 +7662,6 @@ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -9455,34 +9476,47 @@ } }, "prebuild-install": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz", - "integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.4.tgz", + "integrity": "sha512-AkKN+pf4fSEihjapLEEj8n85YIw/tN6BQqkhzbDc0RvEZGdkpJBGMUYx66AAMcPG2KzmPQS7Cm16an4HVBRRMA==", "requires": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", + "minimist": "^1.2.3", "mkdirp": "^0.5.1", "napi-build-utils": "^1.0.1", "node-abi": "^2.7.0", "noop-logger": "^0.1.1", "npmlog": "^4.0.1", - "os-homedir": "^1.0.1", - "pump": "^2.0.1", + "pump": "^3.0.0", "rc": "^1.2.7", - "simple-get": "^2.7.0", - "tar-fs": "^1.13.0", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0", "which-pm-runs": "^1.0.0" }, "dependencies": { - "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { - "decompress-response": "^3.3.0", + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } @@ -9577,6 +9611,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10334,18 +10369,6 @@ "xtend": "^4.0.1" } }, - "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "lodash": "^4.0.0", - "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" - } - }, "sass-loader": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", @@ -10481,9 +10504,9 @@ } }, "sequelize": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.15.1.tgz", - "integrity": "sha512-DCzzJYvJLMKnyf8G3at2A+yM9M2fSQmTmuOYIpCWM8Gjqx3XfgNTd1NkuyPWFoi1/d1AXQsN2VDPXkPczida8A==", + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.18.4.tgz", + "integrity": "sha512-bBmJqpO1H8Z7L0xzITqVo5KHXFI7GmKfGl/5SIPDKsuUMbuZT98s+gyGeaLXpOWGH1ZUO79hvJ8z74vNcxBWHg==", "requires": { "bluebird": "^3.5.0", "cls-bluebird": "^2.1.0", @@ -10666,27 +10689,78 @@ } }, "sharp": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.21.3.tgz", - "integrity": "sha512-5qZk8r+YgfyztLEKkNez20Wynq/Uh1oNyP5T/3gTYwt2lBYGs9iDs5m0yVsZEPm8eVBbAJhS08J1wp/g+Ai1Qw==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.25.3.tgz", + "integrity": "sha512-qV3n30NaBEhAjBhFo+d8h5N4X3DHteFdwxXoWUiubk72G0VKT5fX50nlcawGYjPqfFV4Z2e/G9gDPeSGAdn/gg==", "requires": { - "bindings": "^1.3.1", - "color": "^3.1.0", + "color": "^3.1.2", "detect-libc": "^1.0.3", - "fs-copy-file-sync": "^1.1.1", - "nan": "^2.12.1", + "node-addon-api": "^3.0.0", "npmlog": "^4.1.2", - "prebuild-install": "^5.2.2", - "semver": "^5.6.0", - "simple-get": "^3.0.3", - "tar": "^4.4.8", + "prebuild-install": "^5.3.3", + "semver": "^7.3.2", + "simple-get": "^4.0.0", + "tar": "^6.0.2", "tunnel-agent": "^0.6.0" }, "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "tar": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", + "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -10732,13 +10806,28 @@ "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" }, "simple-get": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.0.3.tgz", - "integrity": "sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz", + "integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==", "requires": { - "decompress-response": "^3.3.0", + "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } } }, "simple-swizzle": { @@ -10968,9 +11057,9 @@ "dev": true }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -10978,15 +11067,15 @@ } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -10994,9 +11083,9 @@ } }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, "spdy": { @@ -11181,9 +11270,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -11474,20 +11563,20 @@ } }, "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", + "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", "requires": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" }, "dependencies": { "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -11496,41 +11585,15 @@ } }, "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" } }, "telegraf": { @@ -11663,11 +11726,6 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -12718,9 +12776,9 @@ } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, "when": { @@ -12738,9 +12796,9 @@ } }, "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "which-pm-runs": { @@ -12827,55 +12885,77 @@ } }, "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" }, "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "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 }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true + }, + "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 + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "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, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "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, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { - "camelcase": "^3.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - } + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } }, "zone.js": { diff --git a/package.json b/package.json index c5fb579..d16b40f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "matrix-dimension", "version": "1.0.0", - "description": "An alternative integrations manager for Riot", + "description": "An alternative integrations manager for Element", "main": "build/app/index.js", "license": "GPL-3.0", "scripts": { @@ -32,7 +32,7 @@ "git-rev-sync": "^1.12.0", "isipaddress": "0.0.2", "js-yaml": "^3.13.1", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "matrix-bot-sdk": "^0.3.8", "matrix-js-snippets": "^0.2.8", "memory-cache": "^0.2.0", @@ -44,9 +44,9 @@ "request-promise": "^4.2.4", "require-dir-all": "^0.4.15", "semver": "^6.0.0", - "sequelize": "^5.15.1", + "sequelize": "^5.18.4", "sequelize-typescript": "^1.0.0", - "sharp": "^0.21.1", + "sharp": "^0.25.3", "split-host": "^0.1.1", "spotify-uri": "^1.0.0", "sqlite3": "^4.0.9", @@ -95,12 +95,12 @@ "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "iso-639-1": "^2.0.5", - "jquery": "^3.4.1", + "jquery": "^3.5.0", "json-loader": "^0.5.7", "mini-css-extract-plugin": "^0.7.0", "ng2-breadcrumbs": "^0.1.281", "ngx-modialog": "^5.0.1", - "node-sass": "^4.12.0", + "node-sass": "^4.14.1", "postcss-cssnext": "^3.1.0", "postcss-import": "^12.0.1", "postcss-loader": "^3.0.0", diff --git a/scripts/convert-newlines.js b/scripts/convert-newlines.js new file mode 100644 index 0000000..8a837db --- /dev/null +++ b/scripts/convert-newlines.js @@ -0,0 +1,23 @@ +const fs = require('fs'); +const util = require('util'); + +(async function () { + if (process.argv.length !== 3) { + console.error('Wrong number of arguments'); + process.exit(-1); + } + + const filePath = process.argv.pop(); + + const fileExists = await util.promisify(fs.exists)(filePath); + + if (fileExists) { + const file = await fs.promises.readFile(filePath, { encoding: 'utf-8' }); + await fs.promises.writeFile( + filePath, + file + .replace(/\r\n/g, '\n') + .replace(/\r/, '\n'), + ); + } +})(); \ No newline at end of file diff --git a/src/api/Webserver.ts b/src/api/Webserver.ts index 79fe588..26550ea 100644 --- a/src/api/Webserver.ts +++ b/src/api/Webserver.ts @@ -40,7 +40,7 @@ export default class Webserver { // We register the default route last to make sure we don't override anything by accident. // We'll pass off all other requests to the web app - this.app.get(/(widgets\/|riot\/|\/)*/, (_req, res) => { + this.app.get(/(widgets\/|riot\/|element\/|\/)*/, (_req, res) => { res.sendFile(path.join(__dirname, "..", "..", "web", "index.html")); }); @@ -93,4 +93,4 @@ export default class Webserver { this.app.listen(config.web.port, config.web.address); LogService.info("Webserver", "API and UI listening on " + config.web.address + ":" + config.web.port); } -} \ No newline at end of file +} diff --git a/src/api/admin/AdminStickerService.ts b/src/api/admin/AdminStickerService.ts index b98dc59..f06577b 100644 --- a/src/api/admin/AdminStickerService.ts +++ b/src/api/admin/AdminStickerService.ts @@ -1,4 +1,4 @@ -import { Context, GET, Path, PathParam, POST, Security, ServiceContext } from "typescript-rest"; +import { Context, GET, Path, PathParam, POST, DELETE, Security, ServiceContext } from "typescript-rest"; import StickerPack from "../../db/models/StickerPack"; import { ApiError } from "../ApiError"; import { DimensionStickerService, MemoryStickerPack } from "../dimension/DimensionStickerService"; @@ -49,6 +49,19 @@ export class AdminStickerService { return {}; // 200 OK } + @DELETE + @Path("packs/:id") + @Security([ROLE_ADMIN]) + public async removePack(@PathParam("id") packId: number): Promise { + const pack = await StickerPack.findByPk(packId); + if (!pack) throw new ApiError(404, "Sticker pack not found"); + + await pack.destroy(); + Cache.for(CACHE_STICKERS).clear(); + + return {}; // 200 OK + } + @POST @Path("packs/import/telegram") @Security([ROLE_USER, ROLE_ADMIN]) @@ -85,16 +98,18 @@ export class AdminStickerService { for (const tgSticker of tgPack.stickers) { LogService.info("AdminStickerService", "Importing sticker from " + tgSticker.url); const buffer = await mx.downloadFromUrl(tgSticker.url); - const png = await sharp(buffer).resize({ - width: 512, - height: 512, + const image = await sharp(buffer); + const metadata = await image.metadata(); + const png = await image.resize({ + width: metadata.width, + height: metadata.height, fit: 'contain', background: 'rgba(0,0,0,0)', }).png().toBuffer(); const mxc = await mx.upload(png, "image/png"); const serverName = mxc.substring("mxc://".length).split("/")[0]; const contentId = mxc.substring("mxc://".length).split("/")[1]; - const thumbMxc = await mx.uploadFromUrl(await mx.getThumbnailUrl(serverName, contentId, 512, 512, "scale", false), "image/png"); + const thumbMxc = await mx.uploadFromUrl(await mx.getThumbnailUrl(serverName, contentId, metadata.width, metadata.height, "scale", false), "image/png"); stickers.push(await Sticker.create({ packId: pack.id, @@ -102,8 +117,8 @@ export class AdminStickerService { description: tgSticker.emoji, imageMxc: mxc, thumbnailMxc: thumbMxc, - thumbnailWidth: 512, - thumbnailHeight: 512, + thumbnailWidth: metadata.width, + thumbnailHeight: metadata.height, mimetype: "image/png", })); diff --git a/src/api/dimension/DimensionBigBlueButtonService.ts b/src/api/dimension/DimensionBigBlueButtonService.ts new file mode 100644 index 0000000..0a4f141 --- /dev/null +++ b/src/api/dimension/DimensionBigBlueButtonService.ts @@ -0,0 +1,207 @@ +import { GET, Path, QueryParam } from "typescript-rest"; +import * as request from "request"; +import { LogService } from "matrix-js-snippets"; +import { URL } from "url"; +import { BigBlueButtonJoinRequest } from "../../models/Widget"; +import { BigBlueButtonJoinResponse } from "../../models/WidgetResponses"; +import { AutoWired } from "typescript-ioc/es6"; +import { ApiError } from "../ApiError"; + +/** + * API for the BigBlueButton widget. + */ +@Path("/api/v1/dimension/bigbluebutton") +@AutoWired +export class DimensionBigBlueButtonService { + + /** + * A regex used for extracting the authenticity token from the HTML of a + * greenlight server response + */ + private authenticityTokenRegexp = new RegExp(`name="authenticity_token" value="([^"]+)".*`); + + // join handles the request from a client to join a BigBlueButton meeting + // + // The client is expected to send a link created by greenlight, the nice UI + // that's recommended to be installed on top of BBB, which is itself a BBB + // client. + // + // This greenlight link is nice, but greenlight unfortunately doesn't have any + // API, and no simple way for us to translate a link from it into a BBB meeting + // URL. It's intended to be loaded by browsers. You enter your preferred name, + // click submit, you potentially wait for the meeting to start, and then you + // finally get the link to join the meeting, and you load that. + // + // As there's no other way to do it, we just reverse-engineer it and pretend + // to be a browser below. We can't do this from the client side as widgets + // run in iframes and browsers can't inspect the content of an iframe if + // it's running on a separate domain. + // + // So the client gets a greenlight URL pasted into it. The flow is then: + // + // + // +---------+ +-----------+ +-------------+ +-----+ + // | Client | | Dimension | | Greenlight | | BBB | + // +---------+ +-----------+ +-------------+ +-----+ + // | | | | + // | | | | + // | | | | + // | | | | + // | /bigbluebutton/join&greenlightUrl=https://.../abc-def-123&fullName=bob | | | + // |---------------------------------------------------------------------------->| | | + // | | | | + // | | GET https://.../abc-def-123 | | + // | |-------------------------------------------------------------------------------------->| | + // | | | | + // | | Have some HTML | | + // | |<--------------------------------------------------------------------------------------| | + // | | | | + // | | Extract authenticity_token from HTML | | + // | |------------------------------------- | | + // | | | | | + // | |<------------------------------------ | | + // | | | | + // | | Extract cookies from HTTP response | | + // | |----------------------------------- | | + // | | | | | + // | |<---------------------------------- | | + // | | | | + // | | POST https://.../abc-def-123&authenticity_token=...&abc-def-123[join_name]=bob | | + // | |-------------------------------------------------------------------------------------->| | + // |===============================================================================================If the meeting has not started yet================================================| + // | | | | + // | | HTML https://.../abc-def-123 Meeting not started | | + // | |<--------------------------------------------------------------------------------------| | + // | | | | + // | 400 MEETING_NOT_STARTED_YET | | | + // |<----------------------------------------------------------------------------| | | + // | | | | + // | | | | + // | Wait a bit and restart the process | | | + // |------------------------------------- | | | + // | | | | | + // |<------------------------------------ | | | + // | | | | + // |=================================================================================================================================================================================| + // | | | | + // | | 302 Location: https://bbb.example.com/join?... | | + // | |<--------------------------------------------------------------------------------------| | + // | | | | + // | | Extract value of Location header | | + // | |--------------------------------- | | + // | | | | | + // | |<-------------------------------- | | + // | | | | + // | https://bbb.example.com/join?... | | | + // |<----------------------------------------------------------------------------| | | + // | | | | + // | GET https://bbb.example.com/join?... | | | + // |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->| + // | | | | + // | | Send back meeting page HTML | | + // |<--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + // + @GET + @Path("join") + public async join( + joinRequest: BigBlueButtonJoinRequest, + @QueryParam("greenlightUrl") greenlightURL: string, + @QueryParam("fullName") fullName: string, + ): Promise { + // Parse the greenlight url and retrieve the path + const greenlightMeetingID = new URL(greenlightURL).pathname; + + LogService.info("BigBlueButton", "URL from client: " + greenlightURL); + LogService.info("BigBlueButton", "MeetingID: " + greenlightMeetingID); + LogService.info("BigBlueButton", "Name given from client: " + fullName); + LogService.info("BigBlueButton", joinRequest); + + // Query the URL the user has given us + let response = await this.doRequest("GET", greenlightURL); + if (!response || !response.body) { + throw new Error("Invalid response from Greenlight server while joining meeting"); + } + + // Attempt to extract the authenticity token + const matches = response.body.match(this.authenticityTokenRegexp); + if (matches.length < 2) { + throw new Error("Unable to find authenticity token for given 'greenlightUrl' parameter"); + } + const authenticityToken = matches[1]; + + // Give the authenticity token and desired name to greenlight, getting the + // join URL in return. Greenlight will send the URL back as a Location: + // header. We want to extract and return the contents of this header, rather + // than following it ourselves + + // Add authenticity token and full name to the query parameters + let queryParams = {authenticity_token: authenticityToken}; + queryParams[`${greenlightMeetingID}[join_name]`] = fullName; + + // Request the updated URL + response = await this.doRequest("POST", greenlightURL, queryParams, "{}", false); + if (!response || !response.body) { + throw new Error("Invalid response from Greenlight server while joining meeting"); + } + + if (!("location" in response.response.headers)) { + // We didn't get a meeting URL back. This could either happen due to an issue with the parameters + // sent to the server... or the meeting simply hasn't started yet. + + // Assume it hasn't started yet. Send a custom error code back to the client informing them to try + // again in a bit + return new ApiError( + 400, + {error: "Unable to find meeting URL in greenlight response"}, + "WAITING_FOR_MEETING_START", + ); + } + + // Return the join URL for the client to load + const joinUrl = response.response.headers["location"]; + LogService.info("BigBlueButton", "Sending back join URL: " + joinUrl) + return {url: joinUrl}; + } + + private async doRequest( + method: string, + url: string, + qs?: any, + body?: any, + followRedirect: boolean = true, + ): Promise { + // Query a URL, expecting an HTML response in return + return new Promise((resolve, reject) => { + request({ + method: method, + url: url, + qs: qs, + body: body, + followRedirect: followRedirect, + jar: true, // remember cookies between requests + json: false, // expect html + }, (err, res, _body) => { + try { + if (err) { + LogService.error("BigBlueButtonWidget", "Error calling " + url); + LogService.error("BigBlueButtonWidget", err); + reject(err); + } else if (!res) { + LogService.error("BigBlueButtonWidget", "There is no response for " + url); + reject(new Error("No response provided - is the service online?")); + } else if (res.statusCode !== 200 && res.statusCode !== 302) { + LogService.error("BigBlueButtonWidget", "Got status code " + res.statusCode + " when calling " + url); + LogService.error("BigBlueButtonWidget", res.body); + reject({body: res.body, status: res.statusCode}); + } else { + resolve({body: res.body, response: res}); + } + } catch (e) { + LogService.error("BigBlueButtonWidget", e); + reject(e); + } + }); + }); + } + +} diff --git a/src/api/matrix/MatrixWellknownService.ts b/src/api/matrix/MatrixWellknownService.ts index 9a543b9..8fedb06 100644 --- a/src/api/matrix/MatrixWellknownService.ts +++ b/src/api/matrix/MatrixWellknownService.ts @@ -24,7 +24,7 @@ export class MatrixWellknownService { public async getIntegrations(): Promise { const parsed = new URL(config.dimension.publicUrl); - parsed.pathname = '/riot'; + parsed.pathname = '/element'; const uiUrl = parsed.toString(); parsed.pathname = '/api/v1/scalar'; @@ -39,4 +39,4 @@ export class MatrixWellknownService { }, }; } -} \ No newline at end of file +} diff --git a/src/api/scalar/ScalarWidgetService.ts b/src/api/scalar/ScalarWidgetService.ts index 75f2fa2..524296b 100644 --- a/src/api/scalar/ScalarWidgetService.ts +++ b/src/api/scalar/ScalarWidgetService.ts @@ -11,7 +11,7 @@ interface UrlPreviewResponse { page_title_cache_item: { expires: string; // "2017-12-18T04:20:04.001806738Z" cached_response_err: string; - cached_title: string; // the actual thing riot uses + cached_title: string; // the actual thing Element uses }; error: { message: string; @@ -68,4 +68,4 @@ export class ScalarWidgetService { }; } } -} \ No newline at end of file +} diff --git a/src/config.ts b/src/config.ts index 63eb5fd..510342b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,6 +17,7 @@ export interface DimensionConfig { database: { file: string; botData: string; + uri: string; }; admins: string[]; goneb: { @@ -38,4 +39,4 @@ export interface DimensionConfig { logging: LogConfig; } -export default config; \ No newline at end of file +export default config; diff --git a/src/db/DimensionStore.ts b/src/db/DimensionStore.ts index b0cda57..9fe4eac 100644 --- a/src/db/DimensionStore.ts +++ b/src/db/DimensionStore.ts @@ -35,14 +35,20 @@ class _DimensionStore { private sequelize: Sequelize; constructor() { - this.sequelize = new Sequelize({ - dialect: 'sqlite', - database: "dimension", - storage: process.env['DIMENSION_DB_PATH'] || config.database.file, - username: "", - password: "", - logging: i => LogService.verbose("DimensionStore [SQL]", i) - }); + if (process.env.DATABASE_URI || config.database.uri ) { + this.sequelize = new Sequelize(process.env.DATABASE_URI || config.database.uri , { + logging: i => LogService.verbose("DimensionStore [SQL]", i) + }); + } else { + this.sequelize = new Sequelize({ + dialect: 'sqlite', + database: "dimension", + storage: process.env['DIMENSION_DB_PATH'] || config.database.file, + username: "", + password: "", + logging: i => LogService.verbose("DimensionStore [SQL]", i) + }); + } this.sequelize.addModels([ User, UserScalarToken, diff --git a/src/db/migrations/20180617113345-AddLoadingArtistStickerPack.ts b/src/db/migrations/20180617113345-AddLoadingArtistStickerPack.ts index a063b15..bd2f780 100644 --- a/src/db/migrations/20180617113345-AddLoadingArtistStickerPack.ts +++ b/src/db/migrations/20180617113345-AddLoadingArtistStickerPack.ts @@ -18,7 +18,9 @@ export default { licensePath: "/licenses/cc_by-nc_4.0.txt", } ])) - .then(packId => { + .then(() => queryInterface.rawSelect('dimension_sticker_packs', { where: { name: "Loading Artist" } }, ['id'])) + .then(packIds => { + const packId = Array.isArray(packIds) ? packIds[0] : packIds; return queryInterface.bulkInsert("dimension_stickers", [ { packId: packId, @@ -446,4 +448,4 @@ export default { down: (_queryInterface: QueryInterface) => { throw new Error("there is no going back"); } -} \ No newline at end of file +} diff --git a/src/db/migrations/20200630165247-AddBigBlueButtonWidget.ts b/src/db/migrations/20200630165247-AddBigBlueButtonWidget.ts new file mode 100644 index 0000000..27c00ba --- /dev/null +++ b/src/db/migrations/20200630165247-AddBigBlueButtonWidget.ts @@ -0,0 +1,23 @@ +import { QueryInterface } from "sequelize"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.bulkInsert("dimension_widgets", [ + { + type: "bigbluebutton", + name: "BigBlueButton", + avatarUrl: "/img/avatars/bigbluebutton.png", + isEnabled: true, + isPublic: true, + description: "Embed a BigBlueButton conference", + } + ])); + }, + down: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.bulkDelete("dimension_widgets", { + type: "bigbluebutton", + })); + } +} diff --git a/src/models/Widget.ts b/src/models/Widget.ts new file mode 100644 index 0000000..3a26ef2 --- /dev/null +++ b/src/models/Widget.ts @@ -0,0 +1,7 @@ +export interface BigBlueButtonJoinRequest { + // A URL supplied by greenlight, BigBlueButton's nice UI project that is itself + // a BigBlueButton client + greenlightUrl: string; + // The name the user wishes to join the meeting with + fullName: string; +} diff --git a/src/models/WidgetResponses.ts b/src/models/WidgetResponses.ts new file mode 100644 index 0000000..854ea31 --- /dev/null +++ b/src/models/WidgetResponses.ts @@ -0,0 +1,4 @@ +export interface BigBlueButtonJoinResponse { + // The meeting URL the client should load to join the meeting + url: string; +} diff --git a/web/app/admin/sticker-packs/sticker-packs.component.html b/web/app/admin/sticker-packs/sticker-packs.component.html index f6c9ca7..4813629 100644 --- a/web/app/admin/sticker-packs/sticker-packs.component.html +++ b/web/app/admin/sticker-packs/sticker-packs.component.html @@ -52,10 +52,13 @@ + + + - \ No newline at end of file + diff --git a/web/app/admin/sticker-packs/sticker-packs.component.scss b/web/app/admin/sticker-packs/sticker-packs.component.scss index d92d2a2..9e19650 100644 --- a/web/app/admin/sticker-packs/sticker-packs.component.scss +++ b/web/app/admin/sticker-packs/sticker-packs.component.scss @@ -7,6 +7,11 @@ tr td:last-child { vertical-align: text-bottom; } +.removeButton { + cursor: pointer; + vertical-align: text-bottom; +} + .telegram-import { margin-bottom: 15px; } diff --git a/web/app/admin/sticker-packs/sticker-packs.component.ts b/web/app/admin/sticker-packs/sticker-packs.component.ts index 8078833..57f7bfb 100644 --- a/web/app/admin/sticker-packs/sticker-packs.component.ts +++ b/web/app/admin/sticker-packs/sticker-packs.component.ts @@ -68,4 +68,22 @@ export class AdminStickerPacksComponent implements OnInit { this.toaster.pop("error", "Error importing sticker pack"); }); } + + public removePack(pack: FE_StickerPack) { + this.isUpdating = true; + this.adminStickers.removePack(pack.id).then(() => { + for (let i = 0; i < this.packs.length; ++i) { + if (this.packs[i].id === pack.id) { + this.packs.splice(i, 1); + break; + } + } + this.isUpdating = false; + this.toaster.pop("success", "Sticker pack removed"); + }).catch(err => { + console.error(err); + this.isUpdating = false; + this.toaster.pop("error", "Error removing sticker pack"); + }); + } } diff --git a/web/app/app.module.ts b/web/app/app.module.ts index 6f5496e..9d9dbf7 100644 --- a/web/app/app.module.ts +++ b/web/app/app.module.ts @@ -118,6 +118,9 @@ import { CKEditorModule } from "@ckeditor/ckeditor5-angular"; import { AdminNewEditTermsComponent } from "./admin/terms/new-edit/new-edit.component"; import { AdminTermsNewEditPublishDialogComponent } from "./admin/terms/new-edit/publish/publish.component"; import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.component"; +import { BigBlueButtonConfigComponent } from "./configs/widget/bigbluebutton/bigbluebutton.widget.component"; +import { BigBlueButtonWidgetWrapperComponent } from "./widget-wrappers/bigbluebutton/bigbluebutton.component"; +import { BigBlueButtonApiService } from "./shared/services/integrations/bigbluebutton-api.service"; @NgModule({ imports: [ @@ -147,7 +150,9 @@ import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.compo FullscreenButtonComponent, VideoWidgetWrapperComponent, JitsiWidgetWrapperComponent, + BigBlueButtonWidgetWrapperComponent, GCalWidgetWrapperComponent, + BigBlueButtonConfigComponent, RiotHomeComponent, IboxComponent, ConfigScreenWidgetComponent, @@ -234,6 +239,7 @@ import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.compo AdminStickersApiService, MediaService, StickerApiService, + BigBlueButtonApiService, AdminTelegramApiService, TelegramApiService, AdminWebhooksApiService, diff --git a/web/app/app.routing.ts b/web/app/app.routing.ts index 47816f6..8bbd11c 100644 --- a/web/app/app.routing.ts +++ b/web/app/app.routing.ts @@ -2,6 +2,8 @@ import { RouterModule, Routes } from "@angular/router"; import { HomeComponent } from "./home/home.component"; import { RiotComponent } from "./riot/riot.component"; import { GenericWidgetWrapperComponent } from "./widget-wrappers/generic/generic.component"; +import { BigBlueButtonWidgetWrapperComponent } from "./widget-wrappers/bigbluebutton/bigbluebutton.component"; +import { BigBlueButtonConfigComponent } from "./configs/widget/bigbluebutton/bigbluebutton.widget.component"; import { VideoWidgetWrapperComponent } from "./widget-wrappers/video/video.component"; import { JitsiWidgetWrapperComponent } from "./widget-wrappers/jitsi/jitsi.component"; import { GCalWidgetWrapperComponent } from "./widget-wrappers/gcal/gcal.component"; @@ -51,6 +53,7 @@ import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.compo const routes: Routes = [ {path: "", component: HomeComponent}, {path: "riot", pathMatch: "full", redirectTo: "riot-app"}, + {path: "element", pathMatch: "full", redirectTo: "riot-app"}, { path: "riot-app", component: RiotComponent, @@ -179,6 +182,11 @@ const routes: Routes = [ component: CustomWidgetConfigComponent, data: {breadcrumb: "Custom Widgets", name: "Custom Widgets"}, }, + { + path: "bigbluebutton", + component: BigBlueButtonConfigComponent, + data: {breadcrumb: "BigBlueButton Widgets", name: "BigBlueButton Widgets"}, + }, { path: "etherpad", component: EtherpadWidgetConfigComponent, @@ -285,6 +293,7 @@ const routes: Routes = [ {path: "generic", component: GenericWidgetWrapperComponent}, {path: "video", component: VideoWidgetWrapperComponent}, {path: "jitsi", component: JitsiWidgetWrapperComponent}, + {path: "bigbluebutton", component: BigBlueButtonWidgetWrapperComponent}, {path: "gcal", component: GCalWidgetWrapperComponent}, {path: "stickerpicker", component: StickerPickerWidgetWrapperComponent}, {path: "generic-fullscreen", component: GenericFullscreenWidgetWrapperComponent}, diff --git a/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.html b/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.html new file mode 100644 index 0000000..a0ab995 --- /dev/null +++ b/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.html @@ -0,0 +1,11 @@ + + + + + diff --git a/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.scss b/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.ts b/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.ts new file mode 100644 index 0000000..ac92217 --- /dev/null +++ b/web/app/configs/widget/bigbluebutton/bigbluebutton.widget.component.ts @@ -0,0 +1,53 @@ +import { WidgetComponent, DISABLE_AUTOMATIC_WRAPPING } from "../widget.component"; +import { WIDGET_BIGBLUEBUTTON, EditableWidget } from "../../../shared/models/widget"; +import { Component } from "@angular/core"; +import { FE_BigBlueButtonWidget } from "../../../shared/models/integration"; +import { SessionStorage } from "../../../shared/SessionStorage"; +import * as url from "url"; + +@Component({ + templateUrl: "bigbluebutton.widget.component.html", + styleUrls: ["bigbluebutton.widget.component.scss"], +}) + +// Configuration of BigBlueButton widgets +export class BigBlueButtonConfigComponent extends WidgetComponent { + private bigBlueButtonWidget: FE_BigBlueButtonWidget = SessionStorage.editIntegration; + + constructor() { + super(WIDGET_BIGBLUEBUTTON, "BigBlueButton Conference", DISABLE_AUTOMATIC_WRAPPING); + } + + protected OnWidgetsDiscovered(widgets: EditableWidget[]) { + for (const widget of widgets) { + widget.data.conferenceUrl = this.templateUrl(widget.url, widget.data); + } + } + + protected OnNewWidgetPrepared(widget: EditableWidget): void { + widget.dimension.newData["conferenceUrl"] = this.bigBlueButtonWidget.options.conferenceUrl; + } + + protected OnWidgetBeforeAdd(widget: EditableWidget) { + this.setWidgetOptions(widget); + } + + protected OnWidgetBeforeEdit(widget: EditableWidget) { + this.setWidgetOptions(widget); + } + + private setWidgetOptions(widget: EditableWidget) { + widget.dimension.newData.url = widget.dimension.newData.conferenceUrl; + + let widgetQueryString = url.format({ + query: { + "conferenceUrl": "$conferenceUrl", + "displayName": "$matrix_display_name", + "avatarUrl": "$matrix_avatar_url", + "userId": "$matrix_user_id", + }, + }); + widgetQueryString = this.decodeParams(widgetQueryString, Object.keys(widget.dimension.newData).map(k => "$" + k)); + widget.dimension.newUrl = window.location.origin + "/widgets/bigbluebutton" + widgetQueryString; + } +} diff --git a/web/app/home/home.component.html b/web/app/home/home.component.html index c5ec4a1..72a58f1 100644 --- a/web/app/home/home.component.html +++ b/web/app/home/home.component.html @@ -16,8 +16,8 @@

Try it out or run your own

- Visit t2bot.io/riot and log in with your Matrix account - or point your Riot config.json at our servers: + Visit element.t2host.io and log in with your Matrix account + or point your Element config.json at our servers:

{{ integrationsConfig }}
@@ -69,6 +69,10 @@ Google Calendar +
+ + BigBlueButton +
Custom Widget @@ -220,19 +224,19 @@ for news and updates. Don't forget to star the repository on GitHub.

-

Here's the configuration options you'll need to update in your Riot config.json:

+

Here's the configuration options you'll need to update in your Element config.json:

{{ integrationsConfig }}

Configuring integrations

If everything is set up correctly, you'll be able to access the admin area of Dimension by clicking - the 3x3 grid in the top right of any room in Riot. The gear icon () in the + the 3x3 grid in the top right of any room in Element. The gear icon () in the top right is where you can configure your bots, bridges, and widgets.

"Could not connect to integrations server" error

- When Riot cannot reach Dimension or Dimension is unable to reach your homeserver an error saying "Could not + When Element cannot reach Dimension or Dimension is unable to reach your homeserver an error saying "Could not contact integrations server" shows up in every room. Before visiting us in #dimension:t2bot.io on Matrix, here's a few things to check: @@ -261,4 +265,4 @@ #dimension:t2bot.io

- \ No newline at end of file + diff --git a/web/app/home/home.component.ts b/web/app/home/home.component.ts index 8536823..e144bed 100644 --- a/web/app/home/home.component.ts +++ b/web/app/home/home.component.ts @@ -11,7 +11,7 @@ export class HomeComponent { public showPromoPage = this.hostname === "https://dimension.t2bot.io"; public integrationsConfig = `` + - `"integrations_ui_url": "${this.hostname}/riot",\n` + + `"integrations_ui_url": "${this.hostname}/element",\n` + `"integrations_rest_url": "${this.hostname}/api/v1/scalar",\n` + `"integrations_widgets_urls": ["${this.hostname}/widgets"],\n` + `"integrations_jitsi_widget_url": "${this.hostname}/widgets/jitsi",\n`; diff --git a/web/app/riot/riot-home/home.component.html b/web/app/riot/riot-home/home.component.html index a96073d..72b42a5 100644 --- a/web/app/riot/riot-home/home.component.html +++ b/web/app/riot/riot-home/home.component.html @@ -18,7 +18,7 @@ Integrations are not encrypted! This means that some information about yourself and the room may be leaked to the bot, bridge, or widget. This information includes the room ID, your display - name, your username, your avatar, information about Riot, and other similar details. Add integrations + name, your username, your avatar, information about Element, and other similar details. Add integrations with caution.
@@ -42,4 +42,4 @@
- \ No newline at end of file + diff --git a/web/app/riot/riot-home/home.component.ts b/web/app/riot/riot-home/home.component.ts index 064c1b2..45a8579 100644 --- a/web/app/riot/riot-home/home.component.ts +++ b/web/app/riot/riot-home/home.component.ts @@ -77,7 +77,7 @@ export class RiotHomeComponent { console.error("No user returned for token. Is the token registered in Dimension?"); this.isError = true; this.isLoading = false; - this.errorMessage = "Could not verify your token. Please try logging out of Riot and back in. Be sure to back up your encryption keys!"; + this.errorMessage = "Could not verify your token. Please try logging out of Element and back in. Be sure to back up your encryption keys!"; } else { this.userId = userId; console.log("Scalar token belongs to " + userId); @@ -189,7 +189,7 @@ export class RiotHomeComponent { console.error(err); this.isError = true; this.isLoading = false; - this.errorMessage = "Unable to set up Dimension. This version of Riot may not supported or there may be a problem with the server."; + this.errorMessage = "Unable to set up Dimension. This version of Element may not supported or there may be a problem with the server."; }); this.stickerApi.getPacks().then(packs => { @@ -265,7 +265,7 @@ export class RiotHomeComponent { case "publicRoom": return this.scalar.getJoinRule(this.roomId).then(payload => { if (!payload.response) { - return Promise.reject("Could not communicate with Riot"); + return Promise.reject("Could not communicate with Element"); } const isPublic = payload.response.join_rule === "public"; if (isPublic !== requirement.expectedValue) { @@ -278,7 +278,7 @@ export class RiotHomeComponent { if (response === true) return Promise.resolve(); if (response.error || response.error.message) return Promise.reject("You cannot modify widgets in this room"); - return Promise.reject("Error communicating with Riot"); + return Promise.reject("Error communicating with Element"); }; let promiseChain = Promise.resolve(); diff --git a/web/app/shared/models/integration.ts b/web/app/shared/models/integration.ts index a841e4d..6d8a444 100644 --- a/web/app/shared/models/integration.ts +++ b/web/app/shared/models/integration.ts @@ -64,6 +64,11 @@ export interface FE_Sticker { }; } +export interface FE_BigBlueButtonJoin { + // The meeting URL the client should load to join the meeting + url: string; +} + export interface FE_StickerConfig { enabled: boolean; stickerBot: string; @@ -88,8 +93,14 @@ export interface FE_JitsiWidget extends FE_Widget { }; } +export interface FE_BigBlueButtonWidget extends FE_Widget { + options: { + conferenceUrl: string; + }; +} + export interface FE_IntegrationRequirement { condition: "publicRoom" | "canSendEventTypes" | "userInRoom"; argument: any; expectedValue: any; -} \ No newline at end of file +} diff --git a/web/app/shared/models/widget.ts b/web/app/shared/models/widget.ts index 37d853a..795189b 100644 --- a/web/app/shared/models/widget.ts +++ b/web/app/shared/models/widget.ts @@ -1,6 +1,7 @@ import { WidgetsResponse } from "./server-client-responses"; export const WIDGET_CUSTOM = ["m.custom", "customwidget", "dimension-customwidget"]; +export const WIDGET_BIGBLUEBUTTON = ["bigbluebutton", "dimension-bigbluebutton"]; export const WIDGET_ETHERPAD = ["m.etherpad", "etherpad", "dimension-etherpad"]; export const WIDGET_GOOGLE_DOCS = ["m.googledoc", "googledocs", "dimension-googledocs"]; export const WIDGET_GOOGLE_CALENDAR = ["m.googlecalendar", "googlecalendar", "dimension-googlecalendar"]; diff --git a/web/app/shared/registry/integrations.registry.ts b/web/app/shared/registry/integrations.registry.ts index 3d902b9..673836b 100644 --- a/web/app/shared/registry/integrations.registry.ts +++ b/web/app/shared/registry/integrations.registry.ts @@ -1,6 +1,7 @@ import { Injectable } from "@angular/core"; import { WIDGET_CUSTOM, + WIDGET_BIGBLUEBUTTON, WIDGET_ETHERPAD, WIDGET_GOOGLE_CALENDAR, WIDGET_GOOGLE_DOCS, @@ -35,6 +36,9 @@ export class IntegrationsRegistry { "custom": { types: WIDGET_CUSTOM, }, + "bigbluebutton": { + types: WIDGET_BIGBLUEBUTTON, + }, "youtube": { types: WIDGET_YOUTUBE }, diff --git a/web/app/shared/services/admin/admin-stickers-api-service.ts b/web/app/shared/services/admin/admin-stickers-api-service.ts index b4ffdb0..b5f2c54 100644 --- a/web/app/shared/services/admin/admin-stickers-api-service.ts +++ b/web/app/shared/services/admin/admin-stickers-api-service.ts @@ -20,4 +20,8 @@ export class AdminStickersApiService extends AuthedApi { public importFromTelegram(packUrl: string): Promise { return this.authedPost("/api/v1/dimension/admin/stickers/packs/import/telegram", {packUrl: packUrl}).toPromise(); } + + public removePack(packId: number): Promise { + return this.authedDelete("/api/v1/dimension/admin/stickers/packs/" + packId).toPromise(); + } } diff --git a/web/app/shared/services/integrations/bigbluebutton-api.service.ts b/web/app/shared/services/integrations/bigbluebutton-api.service.ts new file mode 100644 index 0000000..4a23cfe --- /dev/null +++ b/web/app/shared/services/integrations/bigbluebutton-api.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from "@angular/core"; +import { AuthedApi } from "../authed-api"; +import { FE_BigBlueButtonJoin } from "../../models/integration" +import { HttpClient } from "@angular/common/http"; +import { ApiError } from "../../../../../src/api/ApiError"; + +@Injectable() +export class BigBlueButtonApiService extends AuthedApi { + constructor(http: HttpClient) { + super(http); + } + + public joinMeeting(url: string, name: string): Promise { + return this.authedGet("/api/v1/dimension/bigbluebutton/join", {greenlightUrl: url, fullName: name}).toPromise(); + } +} \ No newline at end of file diff --git a/web/app/shared/services/scalar/scalar-widget.api.ts b/web/app/shared/services/scalar/scalar-widget.api.ts index 5897344..1a96eb4 100644 --- a/web/app/shared/services/scalar/scalar-widget.api.ts +++ b/web/app/shared/services/scalar/scalar-widget.api.ts @@ -68,7 +68,7 @@ export class ScalarWidgetApi { public static sendSetAlwaysOnScreen(alwaysVisible: boolean): void { ScalarWidgetApi.callAction("set_always_on_screen", { - // Send the value here and in data due to a Riot bug. + // Send the value here and in data due to a Element bug. data: { value: alwaysVisible, }, diff --git a/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.html b/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.html new file mode 100644 index 0000000..c42759f --- /dev/null +++ b/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.html @@ -0,0 +1,26 @@ + + +
+
+
+

+
+ +
+

BigBlueButton Conference

+ +
+
+
+
diff --git a/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.scss b/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.scss new file mode 100644 index 0000000..1918e2a --- /dev/null +++ b/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.scss @@ -0,0 +1,32 @@ +// component styles are encapsulated and only applied to their components +@import "../../../style/themes/themes"; + +@include themifyComponent() { + #bigBlueButtonContainer { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + .join-conference-wrapper { + display: table; + position: absolute; + height: 100%; + width: 100%; + background-color: themed(widgetWelcomeBgColor); + } + + .join-conference-boat { + display: table-cell; + vertical-align: middle; + } + + .join-conference-prompt { + margin-left: auto; + margin-right: auto; + width: 90%; + text-align: center; + } +} diff --git a/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.ts b/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.ts new file mode 100644 index 0000000..a423e99 --- /dev/null +++ b/web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.ts @@ -0,0 +1,152 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { WidgetApiService } from "../../shared/services/integrations/widget-api.service"; +import { Subscription } from "rxjs/Subscription"; +import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api"; +import { CapableWidget } from "../capable-widget"; +import { DomSanitizer, SafeUrl } from "@angular/platform-browser"; +import { BigBlueButtonApiService } from "../../shared/services/integrations/bigbluebutton-api.service"; +import { FE_BigBlueButtonJoin } from "../../shared/models/integration"; + +@Component({ + selector: "my-bigbluebutton-widget-wrapper", + templateUrl: "bigbluebutton.component.html", + styleUrls: ["bigbluebutton.component.scss"], +}) +export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implements OnInit, OnDestroy { + + public canEmbed = true; + + /** + * User metadata passed to us by the client + */ + private conferenceUrl: string; + private displayName: string; + private userId: string; + + /** + * The poll period in ms while waiting for a meeting to start + */ + private pollIntervalMillis = 5000; + + /** + * Subscriber for messages from the client via the postMessage API + */ + private bigBlueButtonApiSubscription: Subscription; + + /** + * A status message to display to the user in the widget, typically for loading messages + */ + public statusMessage: string; + + /** + * Whether we are currently in a meeting + */ + private inMeeting: boolean = false; + + /** + * The URL to embed into the iframe + */ + public embedUrl: SafeUrl = null; + + constructor(activatedRoute: ActivatedRoute, + private bigBlueButtonApi: BigBlueButtonApiService, + private widgetApi: WidgetApiService, + private sanitizer: DomSanitizer) { + super(); + this.supportsAlwaysOnScreen = true; + + let params: any = activatedRoute.snapshot.queryParams; + + console.log("BigBlueButton: Given greenlight url: " + params.conferenceUrl); + + this.conferenceUrl = params.conferenceUrl; + this.displayName = params.displayName; + this.userId = params.userId || params.email; // Element uses `email` when placing a conference call + + // Set the widget ID if we have it + ScalarWidgetApi.widgetId = params.widgetId; + } + + public ngOnInit() { + super.ngOnInit(); + } + + public onIframeLoad() { + if (this.inMeeting) { + // The meeting has ended and we've come back full circle + this.inMeeting = false; + this.statusMessage = null; + this.embedUrl = null; + + ScalarWidgetApi.sendSetAlwaysOnScreen(false); + return; + } + + // Have a toggle for whether we're in a meeting. We do this as we don't have a method + // of checking which URL was just loaded in the iframe (due to different origin domains + // and browser security), so we have to guess that it'll always be the second load (the + // first being joining the meeting) + this.inMeeting = true; + + // We've successfully joined the meeting + ScalarWidgetApi.sendSetAlwaysOnScreen(true); + } + + public joinConference(updateStatusMessage: boolean = true) { + if (updateStatusMessage) { + // Inform the user that we're loading their meeting + this.statusMessage = "Joining conference..."; + } + + // Generate a nick to display in the meeting + const joinName = `${this.displayName} (${this.userId})`; + + // Make a request to Dimension requesting the join URL + console.log("BigBlueButton: joining via greenlight url:", this.conferenceUrl); + this.bigBlueButtonApi.joinMeeting(this.conferenceUrl, joinName).then((response) => { + if ("errorCode" in response) { + // This is an instance of ApiError + if (response.errorCode == "WAITING_FOR_MEETING_START") { + // The meeting hasn't started yet + this.statusMessage = "Waiting for conference to start..."; + + // Poll until it has + setTimeout(this.joinConference.bind(this), this.pollIntervalMillis, false); + return; + } + + // Otherwise this is a generic error + this.statusMessage = "An error occurred while loading the meeting"; + } + + const joinUrl = (response as FE_BigBlueButtonJoin).url; + + // Check if the given URL is embeddable + this.widgetApi.isEmbeddable(joinUrl).then(result => { + this.canEmbed = result.canEmbed; + this.statusMessage = null; + + // Embed the return meeting URL, joining the meeting + this.embedUrl = this.sanitizer.bypassSecurityTrustResourceUrl(joinUrl); + + // Inform the client that we would like the meeting to remain visible for its duration + ScalarWidgetApi.sendSetAlwaysOnScreen(true); + }).catch(err => { + console.error(err); + this.canEmbed = false; + this.statusMessage = "Unable to embed meeting"; + }); + }); + } + + public ngOnDestroy() { + if (this.bigBlueButtonApiSubscription) this.bigBlueButtonApiSubscription.unsubscribe(); + } + + protected onCapabilitiesSent(): void { + super.onCapabilitiesSent(); + ScalarWidgetApi.sendSetAlwaysOnScreen(false); + } + +} diff --git a/web/app/widget-wrappers/jitsi/jitsi.component.scss b/web/app/widget-wrappers/jitsi/jitsi.component.scss index 553cb6c..38484f6 100644 --- a/web/app/widget-wrappers/jitsi/jitsi.component.scss +++ b/web/app/widget-wrappers/jitsi/jitsi.component.scss @@ -16,7 +16,7 @@ position: absolute; height: 100%; width: 100%; - background-color: themed(jitsiWelcomeBgColor); + background-color: themed(widgetWelcomeBgColor); } .join-conference-boat { @@ -30,4 +30,4 @@ width: 90%; text-align: center; } -} \ No newline at end of file +} diff --git a/web/app/widget-wrappers/jitsi/jitsi.component.ts b/web/app/widget-wrappers/jitsi/jitsi.component.ts index 8733111..083c983 100644 --- a/web/app/widget-wrappers/jitsi/jitsi.component.ts +++ b/web/app/widget-wrappers/jitsi/jitsi.component.ts @@ -38,9 +38,8 @@ export class JitsiWidgetWrapperComponent extends CapableWidget implements OnInit this.conferenceId = params.conferenceId || params.confId; this.displayName = params.displayName; this.avatarUrl = params.avatarUrl; - this.userId = params.userId || params.email; // Riot uses `email` when placing a conference call + this.userId = params.userId || params.email; // Element uses `email` when placing a conference call this.isAudioOnly = params.isAudioOnly === 'true'; - this.toggleVideo = !this.isAudioOnly; // Set the widget ID if we have it @@ -54,7 +53,7 @@ export class JitsiWidgetWrapperComponent extends CapableWidget implements OnInit $.getScript(widget.options.scriptUrl); if (!this.domain) { - // Always fall back to jitsi.riot.im to maintain compatibility with widgets created by Riot. + // Always fall back to jitsi.riot.im to maintain compatibility with widgets created by Element. this.domain = widget.options.useDomainAsDefault ? widget.options.jitsiDomain : "jitsi.riot.im"; } }); diff --git a/web/app/widget-wrappers/sticker-picker/sticker-picker.component.html b/web/app/widget-wrappers/sticker-picker/sticker-picker.component.html index efe1ad8..9d461af 100644 --- a/web/app/widget-wrappers/sticker-picker/sticker-picker.component.html +++ b/web/app/widget-wrappers/sticker-picker/sticker-picker.component.html @@ -14,7 +14,7 @@
-
+
{{ pack.displayName }}
+
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/web/app/widget-wrappers/sticker-picker/sticker-picker.component.scss b/web/app/widget-wrappers/sticker-picker/sticker-picker.component.scss index aec112a..6ccaaf2 100644 --- a/web/app/widget-wrappers/sticker-picker/sticker-picker.component.scss +++ b/web/app/widget-wrappers/sticker-picker/sticker-picker.component.scss @@ -43,6 +43,7 @@ .sticker-picker { margin: 15px 15px 30px; + padding-bottom: 40px; .sticker-pack { .header { @@ -92,5 +93,33 @@ } } } + + .sticker-pack-list { + position: fixed; + left: 0; + right: 0; + bottom: 0; + background-color: themed(stickerPickerControlBgColor); + border-top: 1px solid themed(stickerPickerShadowColor); + overflow-x: auto; + white-space: nowrap; + padding: 1px 15px; + + .sticker-pack-list-item { + display: inline-block; + cursor: pointer; + padding: 0 3px; + } + + .sticker-pack-list-config { + display: inline-block; + cursor: pointer; + height: 40px; + width: 40px; + padding: 3px; + text-align: center; + vertical-align: middle; + } + } } } \ No newline at end of file diff --git a/web/app/widget-wrappers/sticker-picker/sticker-picker.component.ts b/web/app/widget-wrappers/sticker-picker/sticker-picker.component.ts index 11f705d..c6dde11 100644 --- a/web/app/widget-wrappers/sticker-picker/sticker-picker.component.ts +++ b/web/app/widget-wrappers/sticker-picker/sticker-picker.component.ts @@ -1,6 +1,22 @@ +import { + animate, + state, + style, + transition, + trigger +} from '@angular/animations'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { CapableWidget, WIDGET_API_VERSION_OPENID } from "../capable-widget"; +import { fromEvent } from 'rxjs'; +import { + distinctUntilChanged, + filter, + map, + pairwise, + share, + throttleTime +} from 'rxjs/operators'; import { Subscription } from "rxjs/Subscription"; import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api"; import { StickerApiService } from "../../shared/services/integrations/sticker-api.service"; @@ -14,10 +30,25 @@ import { WIDGET_STICKER_PICKER } from "../../shared/models/widget"; selector: "my-generic-widget-wrapper", templateUrl: "sticker-picker.component.html", styleUrls: ["sticker-picker.component.scss"], + animations: [ + trigger('hideList', [ + state( + 'hidden', + style({ opacity: 0, transform: 'translateY(100%)' }) + ), + state( + 'visible', + style({ opacity: 1, transform: 'translateY(0)' }) + ), + transition('* => *', animate('200ms ease-in')) + ]) + ] }) + export class StickerPickerWidgetWrapperComponent extends CapableWidget implements OnInit, OnDestroy { public isLoading = true; + public isListVisible = true; public authError = false; public packs: FE_UserStickerPack[]; @@ -68,6 +99,28 @@ export class StickerPickerWidgetWrapperComponent extends CapableWidget implement if (this.stickerWidgetApiSubscription) this.stickerWidgetApiSubscription.unsubscribe(); } + public ngAfterViewInit() { + const scroll$ = fromEvent(window, 'scroll').pipe( + throttleTime(10), + map(() => window.pageYOffset), + pairwise(), + map(([y1, y2]): string => (y2 < y1 ? 'up' : 'down')), + distinctUntilChanged(), + share() + ); + + const scrollUp$ = scroll$.pipe( + filter(direction => direction === 'up') + ); + + const scrollDown = scroll$.pipe( + filter(direction => direction === 'down') + ); + + scrollUp$.subscribe(() => (this.isListVisible = true)); + scrollDown.subscribe(() => (this.isListVisible = false)); + } + protected onSupportedVersionsFound(): void { super.onSupportedVersionsFound(); @@ -133,6 +186,16 @@ export class StickerPickerWidgetWrapperComponent extends CapableWidget implement } } + public scrollHorizontal(event: WheelEvent): void { + document.getElementsByClassName('sticker-pack-list')[0].scrollLeft += event.deltaY; + event.preventDefault(); + } + + public scrollToPack(id: string) { + const el = document.getElementById(id); + el.scrollIntoView({behavior: 'smooth'}); + } + public sendSticker(sticker: FE_Sticker, pack: FE_UserStickerPack) { ScalarWidgetApi.sendSticker(sticker, pack); } diff --git a/web/public/img/avatars/bigbluebutton.png b/web/public/img/avatars/bigbluebutton.png new file mode 100644 index 0000000..5c6849a Binary files /dev/null and b/web/public/img/avatars/bigbluebutton.png differ diff --git a/web/public/img/avatars/custombots.png b/web/public/img/avatars/custombots.png index c358eee..254d385 100644 Binary files a/web/public/img/avatars/custombots.png and b/web/public/img/avatars/custombots.png differ diff --git a/web/public/img/avatars/customwidget.png b/web/public/img/avatars/customwidget.png index edadd57..b71d0a0 100644 Binary files a/web/public/img/avatars/customwidget.png and b/web/public/img/avatars/customwidget.png differ diff --git a/web/public/img/avatars/echo.png b/web/public/img/avatars/echo.png index 7b7fbcf..f90ea98 100644 Binary files a/web/public/img/avatars/echo.png and b/web/public/img/avatars/echo.png differ diff --git a/web/public/img/avatars/etherpad.png b/web/public/img/avatars/etherpad.png index 996bfd1..b76f2fc 100644 Binary files a/web/public/img/avatars/etherpad.png and b/web/public/img/avatars/etherpad.png differ diff --git a/web/public/img/avatars/github.png b/web/public/img/avatars/github.png index 93f5cb0..2fe41f3 100644 Binary files a/web/public/img/avatars/github.png and b/web/public/img/avatars/github.png differ diff --git a/web/public/img/avatars/google.png b/web/public/img/avatars/google.png index 5a23e0e..640b5cb 100644 Binary files a/web/public/img/avatars/google.png and b/web/public/img/avatars/google.png differ diff --git a/web/public/img/avatars/googlecalendar.png b/web/public/img/avatars/googlecalendar.png index da64c4c..519f885 100644 Binary files a/web/public/img/avatars/googlecalendar.png and b/web/public/img/avatars/googlecalendar.png differ diff --git a/web/public/img/avatars/grafana.png b/web/public/img/avatars/grafana.png index 48c79b3..247c321 100644 Binary files a/web/public/img/avatars/grafana.png and b/web/public/img/avatars/grafana.png differ diff --git a/web/public/img/avatars/imgur.png b/web/public/img/avatars/imgur.png index a236f43..f3ff589 100644 Binary files a/web/public/img/avatars/imgur.png and b/web/public/img/avatars/imgur.png differ diff --git a/web/public/img/avatars/irc.png b/web/public/img/avatars/irc.png index a97aa81..8cc5c5d 100644 Binary files a/web/public/img/avatars/irc.png and b/web/public/img/avatars/irc.png differ diff --git a/web/public/img/avatars/rssbot.png b/web/public/img/avatars/rssbot.png index 3f883ed..10f765f 100644 Binary files a/web/public/img/avatars/rssbot.png and b/web/public/img/avatars/rssbot.png differ diff --git a/web/public/img/avatars/slack.png b/web/public/img/avatars/slack.png index 1a95146..433b9da 100644 Binary files a/web/public/img/avatars/slack.png and b/web/public/img/avatars/slack.png differ diff --git a/web/public/img/avatars/spotify.png b/web/public/img/avatars/spotify.png index 7397787..bab9c9c 100644 Binary files a/web/public/img/avatars/spotify.png and b/web/public/img/avatars/spotify.png differ diff --git a/web/public/img/avatars/telegram.png b/web/public/img/avatars/telegram.png index 37ca4a5..fdfb2f8 100644 Binary files a/web/public/img/avatars/telegram.png and b/web/public/img/avatars/telegram.png differ diff --git a/web/public/img/avatars/tradingview.png b/web/public/img/avatars/tradingview.png index 4fa6bba..e66d7e6 100644 Binary files a/web/public/img/avatars/tradingview.png and b/web/public/img/avatars/tradingview.png differ diff --git a/web/public/img/avatars/twitch.png b/web/public/img/avatars/twitch.png index a54f6b5..9f03767 100644 Binary files a/web/public/img/avatars/twitch.png and b/web/public/img/avatars/twitch.png differ diff --git a/web/public/img/avatars/webhooks.png b/web/public/img/avatars/webhooks.png index cfb02a7..da32577 100644 Binary files a/web/public/img/avatars/webhooks.png and b/web/public/img/avatars/webhooks.png differ diff --git a/web/public/img/avatars/youtube.png b/web/public/img/avatars/youtube.png index 512d0b6..9755963 100644 Binary files a/web/public/img/avatars/youtube.png and b/web/public/img/avatars/youtube.png differ diff --git a/web/public/img/logo/banner-sm.png b/web/public/img/logo/banner-sm.png index 3988a17..5fb3b8c 100644 Binary files a/web/public/img/logo/banner-sm.png and b/web/public/img/logo/banner-sm.png differ diff --git a/web/public/img/logo/favicon/safari-pinned-tab.svg b/web/public/img/logo/favicon/safari-pinned-tab.svg index 41f51ee..d50e862 100644 --- a/web/public/img/logo/favicon/safari-pinned-tab.svg +++ b/web/public/img/logo/favicon/safari-pinned-tab.svg @@ -1,15 +1 @@ - - - - - Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - +Created by potrace 1.11, written by Peter Selinger 2001-2013 \ No newline at end of file diff --git a/web/public/img/logo/made-for-matrix.svg b/web/public/img/logo/made-for-matrix.svg index a246f6b..4b92f3e 100644 --- a/web/public/img/logo/made-for-matrix.svg +++ b/web/public/img/logo/made-for-matrix.svg @@ -1,6 +1 @@ - - made_for_matrix - - - - \ No newline at end of file +made_for_matrix \ No newline at end of file diff --git a/web/public/img/no_stickers.png b/web/public/img/no_stickers.png index f865c1a..e4c47d4 100644 Binary files a/web/public/img/no_stickers.png and b/web/public/img/no_stickers.png differ diff --git a/web/public/img/screenshot.png b/web/public/img/screenshot.png index 37979cb..03c5dbe 100644 Binary files a/web/public/img/screenshot.png and b/web/public/img/screenshot.png differ diff --git a/web/style/_riot.scss b/web/style/_riot.scss index eb869ec..9ab5198 100644 --- a/web/style/_riot.scss +++ b/web/style/_riot.scss @@ -1,4 +1,4 @@ -// The CSS for the Riot breadcrumb is specified here to ensure that it's style can be overridden. +// The CSS for the Element breadcrumb is specified here to ensure that it's style can be overridden. // In it's current position (as a component), the component-level stylesheet cannot access the // elements, so we specify it in a more generic location. @import "themes/themes"; @@ -59,4 +59,4 @@ color: themed(quickActionHoverColor); } } -} \ No newline at end of file +} diff --git a/web/style/themes/dark.scss b/web/style/themes/dark.scss index cf6abfa..d360e43 100644 --- a/web/style/themes/dark.scss +++ b/web/style/themes/dark.scss @@ -48,7 +48,7 @@ $theme_dark: ( stickerPickerStickerBgColor: #fff, stickerPickerShadowColor: hsla(0, 0%, 0%, 0.2), - jitsiWelcomeBgColor: #fff, + widgetWelcomeBgColor: #fff, troubleshooterBgColor: #2d2d2d, troubleshooterNeutralColor: rgb(205, 215, 222), diff --git a/web/style/themes/light.scss b/web/style/themes/light.scss index 9c54a6f..c1e6a0b 100644 --- a/web/style/themes/light.scss +++ b/web/style/themes/light.scss @@ -48,7 +48,7 @@ $theme_light: ( stickerPickerStickerBgColor: #fff, stickerPickerShadowColor: hsla(0, 0%, 0%, 0.2), - jitsiWelcomeBgColor: #fff, + widgetWelcomeBgColor: #fff, troubleshooterBgColor: #fff, troubleshooterNeutralColor: rgb(205, 215, 222), @@ -86,4 +86,4 @@ $theme_light: ( appserviceConfigPreFgColor: rgb(41, 43, 44), appserviceConfigPreBorderColor: #ccc, appserviceConfigPreBgColor: #eee, -); \ No newline at end of file +);