diff --git a/.gitignore b/.gitignore index 07545ca..3d0f9d9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,12 @@ db/*.db start.sh config/integrations/*_development.yaml config/integrations/*_production.yaml +build/ +dimension.db +src/**/*.js +src/**/*.js.map +web/**/*.js +web/**/*.js.map # Logs logs diff --git a/.travis.yml b/.travis.yml index 49b9257..af5dd3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,13 @@ language: node_js node_js: - - "6" + - "8" env: - NODE_ENV=development before_install: - npm i -g npm +- npm i -g typescript install: - npm install script: - npm run build + - npm run lint diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..36789b7 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,53 @@ +# 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. + +**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) + +## Running + +The prerequisites for development are the same as running Dimension in a production environment. + +``` +# Edit the configuration to your specifications. +# Be sure to add yourself as an admin! +cp config/default.yaml config/development.yaml +nano config/development.yaml + +# Run the webserver (it watches for changes) +npm run start:web + +# Run the backend (does not watch for changes) +npm run build:app && node build/app/index.js +``` + +## Backend Architecture + +Integrations are defined into one of four categories: +* Simple bots - Bots that can be invited to the room and left alone (Imgur, Giphy, etc) +* Complex bots - Bots that require some sort of per-room configuration (RSS, Github, etc) +* 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 +breaks out go-neb specifically as it's configuration is fairly involved. + +The backend has 3 major layers: +* The webserver (where all the requests come from) +* 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 +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 +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 +kept small and generic where possible (almost always matching the Service classes in the backend). Components are more of +a judgement call and should be split out where it makes sense. For example, it doesn't make sense to create a component +for every instance where an `ngFor` is used because the number of components would be astronomical. diff --git a/INTEGRATIONS.md b/INTEGRATIONS.md deleted file mode 100644 index 912035c..0000000 --- a/INTEGRATIONS.md +++ /dev/null @@ -1,64 +0,0 @@ -# Integrations in Dimension - -Integrations take the form of bots, bridges, widgets, and other tools that enhance user experience in particular rooms. This document goes through the various setup procedures for the different types of integrations. - -Several integrations already have configuration files in the `config/integrations` folder. Each default configuration has additional information as to how to configure it. Some integrations are disabled by default and can be found in the `config/integrations/samples` folder. - -## Simple Bots - -A simple bot is a bot that requires no configuration by users. It simply exists in the room and provides some minor functionality. Examples of this are the Giphy, Guggy, and Wikipedia bots. - -1. Create a new configuration file in `config/integrations` for the bot. -2. Put the following contents in the configuration file: - ``` - # This is required. Leave it as "bot" for this configuration. - type: "bot" - - # We should probably make sure it is enabled too ;) - enabled: true - - # This is a unique key to identify your bot. For example, "giphy". - integrationType: "YOUR_BOT_NAME" - - # The matrix user ID for the bot. This is likely to be on your server. - userId: "@userID:server.com" - - # The name to use in the UI for Dimension. Try to keep this short. - name: "Some Friendly Bot" - - # A brief description of the bot. These are best kept under 60 characters. - about: "Use `!some command` to interact with the bot" - - # A logo for your bot. We'll upload this in a moment. - avatar: "img/avatars/YOUR_BOT_NAME.png" - - # This is the actual bot configuration so Dimension can control some aspects of the bot's behaviour (leaving rooms, etc). - hosted: - # The URL to the client/server API. This is the "Homeserver URL" in Riot. - homeserverUrl: "https://matrix.org" - - # The access token for the bot. For information on how to get this, visit https://t2bot.io/docs/access_tokens - accessToken: "your_matrix_access_token_here" - ``` -3. Upload/copy the logo for the bot to the `img/avatars` folder, as mentioned above. -4. Restart Dimension - -Your bot should now be in Dimension! - -## Complex Bots - -Complex bots are the bots that require configuration within Dimension in order to work. Examples include the Github bot or RSS bot. Currently these bots are not as easy to set up in Dimension as they could be, and require a developer to write code for Dimension to understand the requirements. - -To request a particular complex bot to be supported, please open a new issue on Github. - -## Bridges - -Bridges allow people in matrix to talk to people not in matrix, and vice-versa. Like complex bots, bridges require additional code in Dimension in order to be supported. - -To request a particular bridge to be supported, please open a new issue on Github. - -## Widgets - -Widgets are web applications that can be embedded in a room for users to interact with. Like bridges and complex bots, widgets require additional code in Dimension to be supported. - -To request a particular widget, please open a new issue on Github. diff --git a/README.md b/README.md index 9e6bf2e..c66e91e 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,10 @@ -# Dimension - +![dimension](https://t2bot.io/_matrix/media/r0/download/t2l.io/b3101d429588673087f457a4bdd52f45) + + [![TravisCI badge](https://travis-ci.org/turt2live/matrix-dimension.svg?branch=master)](https://travis-ci.org/turt2live/matrix-dimension) +[![#dimension:t2bot.io](https://img.shields.io/badge/matrix-%23dimension:t2bot.io-brightgreen.svg)](https://matrix.to/#/#dimension:t2bot.io) -An alternative integrations manager for [Riot](https://riot.im). Join us on matrix: [#dimension:t2l.io](https://matrix.to/#/#dimension:t2l.io) - -![screenshot](https://t2bot.io/_matrix/media/v1/download/t2l.io/kWDyaWXqdsjOJgGYAMMRgGiE) - -# ⚠️ Dimension is in Alpha ⚠️ - -Dimension supports some bridges and bots, however using Dimension in a production scenario is not recommended. Dimension uses features available in recent builds of Riot and may not work on older versions. - -There are plans on the matrix.org front to better support integration managers. Those changes may require an updated homeserver and Riot when made available. +An open source integrations manager for matrix clients, like Riot. # Configuring Riot to use Dimension @@ -22,29 +16,45 @@ Change the values in Riot's `config.json` as shown below. If you do not have a ` "integrations_widgets_urls": ["https://dimension.t2bot.io/widgets"], ``` -The remaining settings should be tailored for your Riot deployment. - -# Building - -To create a production build of Dimension, run `npm run build`. For development environments, see the Development section below. +The remaining settings should be tailored for your Riot deployment. If you're self-hosting Dimension, replace "dimension.t2bot.io" with your Dimension URL. # Running your own -1. Run `npm run build` -2. Copy `config/default.yaml` to `config/production.yaml` and edit `config/production.yaml` -3. Edit any integration settings in `config/integrations` -4. Run Dimension with `NODE_ENV=production node app.js` +Prerequisites: +* [NodeJS](https://nodejs.org/en/download/) 8 or higher +* npm 5 or higher (`npm install -g npm@latest`) +* A webserver running Riot or another supported client -Dimension is now available on the port/host you configured. +```bash +# Download dimension +git clone https://github.com/turt2live/matrix-dimension.git +cd matrix-dimension + +# Edit the configuration to your specifications. +# Be sure to add yourself as an admin! +cp config/default.yaml config/production.yaml +nano config/production.yaml + +# Run +NODE_ENV=production npm run start:app +``` + +If you didn't change the port, Dimension should now be running on port 8184. It's best to set up your environment so that Dimension runs on a dedicated subdomain that *is not* the same as your Riot domain. This is to help keep Riot and Dimension safe and secure. + +In your Riot `config.json`, set the integration manager to be your Dimension URL. Replace `dimension.t2bot.io` in the example above (under "Configuring Riot to use Dimension") with your Dimension URLs. + +After Riot has been configured to use Dimension, refresh Riot and click the "Integrations" button in the top right of the room. It should be an icon that looks like this: + +![3x3 square](https://t2bot.io/_matrix/media/r0/download/t2l.io/gOgboDPEMfiYOQryYwvvHkFz) + +That button should open Dimension. If you've configured everything correctly, you'll see a gear icon in the top right of the window - click this to start editing your integrations. ### Running Dimension behind nginx -1. Run `npm run build` -2. Copy `config/default.yaml` to `config/production.yaml` and edit `config/production.yaml` -3. Edit any integration settings in `config/integrations` -4. Set the host for Dimension to listen on to `localhost` or `127.0.0.1` -5. Run Dimension with `NODE_ENV=production node app.js` -6. Set up the following reverse proxy information as applicable +1. Follow the steps outlined above. +2. Set the host for Dimension to listen on to `localhost` or `127.0.0.1` +3. Restart Dimension (`CTRL+C` and run `NODE_ENV=production npm run start:app` again) +4. Set up the following reverse proxy information as applicable ``` location / { proxy_set_header X-Forwarded-For $remote_addr; @@ -53,54 +63,30 @@ Dimension is now available on the port/host you configured. ``` Be sure to also configure any SSL offloading. +### "Could not contact integrations server" error + +1. **Check that federation is enabled and working on your homeserver.** Even in a private, or non-federated environment, the federation API still needs to be accessible. If federation is a major concern, limit the servers that can use the API by IP or install Dimension on the same server as your homeserver, only exposing federation to localhost. +2. **Check your SRV records.** If you are using SRV records to point to your federation port, make sure that the hostname and port are correct, and that HTTPS is listening on that port. Dimension will use the first record it sees and will only communicate over HTTPS. +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. + # Development -1. Copy `config/default.yaml` to `config/development.yaml` and make any edits -2. Run Dimension with `NODE_ENV=development node app.js` -3. Run the web app with `npm run dev` +For more information about working on Dimension, see DEVELOPMENT.md. # 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. However, there are a few cases where running your own makes more sense: -* Wanting to self-host all aspects of your Riot install +* Wanting to self-host all aspects of your services (client, homeserver, and integrations) * Wanting to advertise custom bots specific to your homeserver -* Corporate or closed environments where Modular's integrations won't work +* Corporate or closed environments where the default integration manager won't work # How do integration managers work? Integration managers sit between your users and your integrations (bots, bridges, etc). It helps guide users through the configuration of your integrations for their rooms. The integrations manager can only manage integrations it is configured for. For example, Modular can only provide configuration for the bridges and bots running on matrix.org, while Dimension can provide configuration for your own bots and bridges. The infrastructure diagram looks something like this: -``` -+-----------+ +----------------------+ +--------------------+ -| |========>| |=========================>| | -| | | Integrations Manager | | Bots, bridges, etc | -| | | (Dimension) | +-------------+ | (go-neb, irc, etc) | -| Clients | | |===>| |<=====>| | -| (Riot) | +----------------------+ | Homeserver | +--------------------+ -| | | (synapse) | -| |============client/server API=======>| | -+-----------+ +-------------+ -``` - -# Common Problems / Setup Questions - -Dimension uses unstable and undocumented parts of Riot and can sometimes be a bit difficult to set up. If you're running into issues, check the solutions below. If you're still having issues, come by [#dimension:t2bot.io](https://matrix.to/#/#dimension:t2bot.io) and we can help you out. - -## Setting up integrations (including custom) - -The INTEGRATIONS.md file in this repository explains how to add custom integrations. For assistance, please visit [#dimension:t2bot.io](https://matrix.to/#/#dimension:t2bot.io) - -## "Could not contact integrations server" - -1. **Check that federation is enabled and working on your homeserver.** Even in a private, or non-federated environment, the federation API still needs to be accessible. If federation is a major concern, limit the servers that can use the API by IP or install Dimension on the same server as your homeserver, only exposing federation to localhost. -2. **Check your SRV records.** If you are using SRV records to point to your federation port, make sure that the hostname and port are correct, and that HTTPS is listening on that port. Dimension will use the first record it sees and will only communicate over HTTPS. -3. **Log out of Riot and log back in.** When switching from the default integrations manager (Scalar) to Dimension the authentication tokens can change. Logging out and back in will reset this token, allowing Dimension to work. More advanced users can delete the "mx_scalar_token" localstorage key. - -## Turning off matrix.org/Scalar dependency - -To completely disconnect Dimension from using the matrix.org bots and bridges, remove the `vector` upstream from your config. This will force anything using the upstream (matrix.org bots and bridges) to not load. +![infrastructure](https://t2bot.io/_matrix/media/r0/download/t2l.io/3bb5674d85ee22c070e36be0d9582b4d) # License diff --git a/app.js b/app.js deleted file mode 100644 index 7a2003b..0000000 --- a/app.js +++ /dev/null @@ -1,17 +0,0 @@ -var log = require("./src/util/LogService"); -var Dimension = require("./src/Dimension"); -var DimensionStore = require("./src/storage/DimensionStore"); -var DemoBot = require("./src/matrix/DemoBot"); -var config = require("config"); - -log.info("app", "Bootstrapping Dimension..."); -var db = new DimensionStore(); -db.prepare().then(() => { - Dimension.start(db); - - if (config.get("demobot.enabled")) { - log.info("app", "Demo bot enabled - starting up"); - var bot = new DemoBot(config.get("demobot.homeserverUrl"), config.get("demobot.userId"), config.get("demobot.accessToken")); - bot.start(); - } -}, err => log.error("app", err)).catch(err => log.error("app", err)); \ No newline at end of file diff --git a/config/database.json b/config/database.json deleted file mode 100644 index f2abade..0000000 --- a/config/database.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "defaultEnv": { - "ENV": "NODE_ENV" - }, - "development": { - "driver": "sqlite", - "filename": "db/development.db" - }, - "production": { - "driver": "sqlite", - "filename": "db/production.db" - } -} \ No newline at end of file diff --git a/config/default.yaml b/config/default.yaml index 7824d86..cdf4eec 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -1,8 +1,45 @@ -# The web settings for the service (API and UI) +# The web settings for the service (API and UI). +# It is best to have this run on localhost and use a reverse proxy to access Dimension. web: port: 8184 address: '0.0.0.0' +# Homeserver configuration +homeserver: + # The domain name of the homeserver. This is used in many places, such as with go-neb + # setups, to identify the homeserver. + name: "t2bot.io" + + # The URL that Dimension, go-neb, and other services provisioned by Dimension should + # use to access the homeserver with. + clientServerUrl: "https://t2bot.io" + + # The URL that Dimension should use when trying to communicate with federated APIs on + # the homeserver. If not supplied or left empty Dimension will try to resolve the address + # through the normal federation process. + #federationUrl: "https://t2bot.io:8448" + + # The access token Dimension should use for miscellaneous access to the homeserver. This + # should be for a valid user. + 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. +admins: + - "@someone:domain.com" + +# IPs and CIDR ranges listed here will be blocked from being widgets. +# Note: Widgets may still be embedded with restricted content, although not through Dimension directly. +widgetBlacklist: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + - 127.0.0.0/8 + +# Where the database for Dimension is +database: + file: "dimension.db" + # Settings for controlling how logging works logging: file: logs/dimension.log @@ -11,29 +48,4 @@ logging: fileLevel: verbose rotate: size: 52428800 # bytes, default is 50mb - count: 5 - -# Demo bot configuration. Used purely to show how to configure a self-hosted bot in Dimension -demobot: - enabled: false - userId: "@dimension:t2bot.io" - homeserverUrl: "https://t2bot.io" - accessToken: "something" - -# Upstream configuration. This should almost never change. -upstreams: -- name: vector - url: "https://scalar.vector.im/api" - -# Homeserver configuration (used to proxy some requests to the homeserver for processing) -homeserver: - name: "t2bot.io" - accessToken: "something" - -# IPs and CIDR ranges listed here will be blocked from being widgets. -# Note: Widgets may still be embedded with restricted content, although not through Dimension directly. -widgetBlacklist: -- 10.0.0.0/8 -- 172.16.0.0/12 -- 192.168.0.0/16 -- 127.0.0.0/8 \ No newline at end of file + count: 5 \ No newline at end of file diff --git a/config/integrations/circleci.yaml b/config/integrations/circleci.yaml deleted file mode 100644 index f0941c5..0000000 --- a/config/integrations/circleci.yaml +++ /dev/null @@ -1,8 +0,0 @@ -type: "complex-bot" -integrationType: "circleci" -enabled: false # disabled because the API is considered unstable/inoperable. Use at your own risk! -name: "CircleCI" -about: "Sends CircleCI build results into the room" -avatar: "img/avatars/circleci.png" -upstream: - type: "vector" diff --git a/config/integrations/custom_widget.yaml b/config/integrations/custom_widget.yaml deleted file mode 100644 index d41ce3d..0000000 --- a/config/integrations/custom_widget.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# All this configuration does is make "Custom Widget" available in the UI -type: "widget" -integrationType: "customwidget" -enabled: true -name: "Custom Widgets" -about: "A webpage embedded in the room." -avatar: "img/avatars/customwidget.png" diff --git a/config/integrations/etherpad_widget.yaml b/config/integrations/etherpad_widget.yaml deleted file mode 100644 index 1b69147..0000000 --- a/config/integrations/etherpad_widget.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# All this configuration does is make "Etherpad Widget" available in the UI -type: "widget" -integrationType: "etherpad" -enabled: true -name: "Etherpad" -about: "Etherpad is a collaborative text editor. With this widget you can embed Etherpad directly in to your Matrix chat rooms so that you can securely collaborate on documents." -avatar: "img/avatars/etherpad.png" - -# This is the URL people will be provided when adding a new Etherpad widget. The supported variables -# are $roomId and $padName. Both will end up being URL encoded. -defaultUrl: "https://demo.riot.im/etherpad/p/$roomId_$padName" \ No newline at end of file diff --git a/config/integrations/giphy.yaml b/config/integrations/giphy.yaml deleted file mode 100644 index a408508..0000000 --- a/config/integrations/giphy.yaml +++ /dev/null @@ -1,19 +0,0 @@ -type: "bot" -integrationType: "giphy" -enabled: true -userId: "@neb_giphy:matrix.org" -name: "Giphy" -about: "Use `!giphy query` to find an animated GIF on demand" -avatar: "img/avatars/giphy.png" - -# This integration can be hosted in one of two ways: self-hosted or using vector.im's version. -# By default, vector.im's version is used, but you can host your own by uncommenting the `hosted` -# section below. Be sure to comment out or remove the `upstream` section below if you're self-hosting. -# Self-hosted bots are expected to auto-respond to invites. - -upstream: - type: "vector" - id: "giphy" -#hosted: -# homeserverUrl: "https://t2bot.io" -# accessToken: "your_matrix_access_token_here" diff --git a/config/integrations/github.yaml.disabled b/config/integrations/github.yaml.disabled deleted file mode 100644 index 4ef0184..0000000 --- a/config/integrations/github.yaml.disabled +++ /dev/null @@ -1,8 +0,0 @@ -type: "complex-bot" -integrationType: "github" -enabled: true -name: "Github" -about: "Track changes to your projects and create new issues using `!github create`" -avatar: "img/avatars/github.png" -upstream: - type: "vector" diff --git a/config/integrations/google.yaml b/config/integrations/google.yaml deleted file mode 100644 index 97d4e5c..0000000 --- a/config/integrations/google.yaml +++ /dev/null @@ -1,19 +0,0 @@ -type: "bot" -integrationType: "google" -enabled: true -userId: "@_neb_google:matrix.org" -name: "Google" -about: "Use `!google image query` to find an image from Google" -avatar: "img/avatars/google.png" - -# This integration can be hosted in one of two ways: self-hosted or using vector.im's version. -# By default, vector.im's version is used, but you can host your own by uncommenting the `hosted` -# section below. Be sure to comment out or remove the `upstream` section below if you're self-hosting. -# Self-hosted bots are expected to auto-respond to invites. - -upstream: - type: "vector" - id: "google" -#hosted: -# homeserverUrl: "https://t2bot.io" -# accessToken: "your_matrix_access_token_here" diff --git a/config/integrations/googlecalendar_widget.yaml b/config/integrations/googlecalendar_widget.yaml deleted file mode 100644 index 4b12fd3..0000000 --- a/config/integrations/googlecalendar_widget.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# All this configuration does is make "Google Calendar Widget" available in the UI -type: "widget" -integrationType: "googlecalendar" -enabled: true -name: "Google Calendar" -about: "Share upcoming events in your room with a Google Calendar" -avatar: "img/avatars/googlecalendar.png" \ No newline at end of file diff --git a/config/integrations/googledocs_widget.yaml b/config/integrations/googledocs_widget.yaml deleted file mode 100644 index 170f15f..0000000 --- a/config/integrations/googledocs_widget.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# All this configuration does is make "Google Docs Widget" available in the UI -type: "widget" -integrationType: "googledocs" -enabled: true -name: "Google Docs" -about: "Collaborate on and share documents using Google Docs" -avatar: "img/avatars/googledocs.png" \ No newline at end of file diff --git a/config/integrations/guggy.yaml b/config/integrations/guggy.yaml deleted file mode 100644 index b759fbc..0000000 --- a/config/integrations/guggy.yaml +++ /dev/null @@ -1,19 +0,0 @@ -type: "bot" -integrationType: "guggy" -enabled: true -userId: "@_neb_guggy:matrix.org" -name: "Guggy" -about: "Use `!guggy sentence` to create an animated GIF from a sentence" -avatar: "img/avatars/guggy.png" - -# This integration can be hosted in one of two ways: self-hosted or using vector.im's version. -# By default, vector.im's version is used, but you can host your own by uncommenting the `hosted` -# section below. Be sure to comment out or remove the `upstream` section below if you're self-hosting. -# Self-hosted bots are expected to auto-respond to invites. - -upstream: - type: "vector" - id: "guggy" -#hosted: -# homeserverUrl: "https://t2bot.io" -# accessToken: "your_matrix_access_token_here" diff --git a/config/integrations/imgur.yaml b/config/integrations/imgur.yaml deleted file mode 100644 index 0e9669d..0000000 --- a/config/integrations/imgur.yaml +++ /dev/null @@ -1,19 +0,0 @@ -type: "bot" -integrationType: "imgur" -enabled: true -userId: "@_neb_imgur:matrix.org" -name: "Imgur" -about: "Use `!imgur query` to find an image from Imgur" -avatar: "img/avatars/imgur.png" - -# This integration can be hosted in one of two ways: self-hosted or using vector.im's version. -# By default, vector.im's version is used, but you can host your own by uncommenting the `hosted` -# section below. Be sure to comment out or remove the `upstream` section below if you're self-hosting. -# Self-hosted bots are expected to auto-respond to invites. - -upstream: - type: "vector" - id: "imgur" -#hosted: -# homeserverUrl: "https://t2bot.io" -# accessToken: "your_matrix_access_token_here" diff --git a/config/integrations/irc_bridge.yaml b/config/integrations/irc_bridge.yaml deleted file mode 100644 index 0aca31a..0000000 --- a/config/integrations/irc_bridge.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: "bridge" -integrationType: "irc" -enabled: true -name: "IRC" -about: "Bridges IRC channels to the room" -avatar: "img/avatars/irc.png" -requirements: - joinRule: 'public' -upstream: - type: "vector" diff --git a/config/integrations/jitsi_widget.yaml b/config/integrations/jitsi_widget.yaml deleted file mode 100644 index 568c2b7..0000000 --- a/config/integrations/jitsi_widget.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# All this configuration does is make "Jitsi Widget" available in the UI -type: "widget" -integrationType: "jitsi" -enabled: true -name: "Jitsi" -about: "Add video conferencing to your room with Jitsi" -avatar: "img/avatars/jitsi.png" - -# This is the domain that will be used to construct the Jitsi widget. It must be just the domain. -# The default is meet.jit.si The Riot.IM instance is at jitsi.riot.im -jitsiDomain: "jitsi.riot.im" - -# This is the path to the external API script. Usually the domain can be replaced with your custom -# domain above without any other modifications. -scriptUrl: "https://jitsi.riot.im/libs/external_api.min.js" \ No newline at end of file diff --git a/config/integrations/rssbot.yaml b/config/integrations/rssbot.yaml deleted file mode 100644 index db559b5..0000000 --- a/config/integrations/rssbot.yaml +++ /dev/null @@ -1,8 +0,0 @@ -type: "complex-bot" -integrationType: "rss" -enabled: true -name: "RSS Bot" -about: "Tracks any Atom/RSS feed and sends new items into this room" -avatar: "img/avatars/rssbot.png" -upstream: - type: "vector" diff --git a/config/integrations/samples/dimension_demo_bot.yaml b/config/integrations/samples/dimension_demo_bot.yaml deleted file mode 100644 index 25d7e01..0000000 --- a/config/integrations/samples/dimension_demo_bot.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: "bot" -integrationType: "demo" -enabled: false -userId: "@dimension:t2bot.io" -name: "Demo Bot" -about: "A bot that has no functionality. This is just a demonstration on the config." -avatar: "img/avatars/demobot.png" -hosted: - homeserverUrl: "https://t2bot.io" - accessToken: "your_matrix_access_token_here" diff --git a/config/integrations/samples/pollbot.yaml b/config/integrations/samples/pollbot.yaml deleted file mode 100644 index 25a042f..0000000 --- a/config/integrations/samples/pollbot.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: "bot" -integrationType: "pollbot" -enabled: false -userId: "@pollbot:t2bot.io" -name: "Poll Bot" -about: "A bot to poll users in a room. Use `!pollhelp` for more information" -avatar: "img/avatars/pollbot.png" -hosted: - homeserverUrl: "https://t2bot.io" - accessToken: "your_matrix_access_token_here" diff --git a/config/integrations/travisci.yaml b/config/integrations/travisci.yaml deleted file mode 100644 index 54e872a..0000000 --- a/config/integrations/travisci.yaml +++ /dev/null @@ -1,8 +0,0 @@ -type: "complex-bot" -integrationType: "travisci" -enabled: true -name: "Travis CI" -about: "Sends Travis CI build results into the room" -avatar: "img/avatars/travisci.png" -upstream: - type: "vector" diff --git a/config/integrations/twitch_widget.yaml b/config/integrations/twitch_widget.yaml deleted file mode 100644 index 46ce899..0000000 --- a/config/integrations/twitch_widget.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# All this configuration does is make "Twitch Widget" available in the UI -type: "widget" -integrationType: "twitch" -enabled: true -name: "Twitch Livestream" -about: "Embed a Twitch Livestream" -avatar: "img/avatars/twitch.png" diff --git a/config/integrations/wikipedia.yaml b/config/integrations/wikipedia.yaml deleted file mode 100644 index 078f283..0000000 --- a/config/integrations/wikipedia.yaml +++ /dev/null @@ -1,19 +0,0 @@ -type: "bot" -integrationType: "wikipedia" -enabled: true -userId: "@_neb_wikipedia:matrix.org" -name: "Wikipedia" -about: "Use `!wikipedia query` to find something from Wikipedia" -avatar: "img/avatars/wikipedia.png" - -# This integration can be hosted in one of two ways: self-hosted or using vector.im's version. -# By default, vector.im's version is used, but you can host your own by uncommenting the `hosted` -# section below. Be sure to comment out or remove the `upstream` section below if you're self-hosting. -# Self-hosted bots are expected to auto-respond to invites. - -upstream: - type: "vector" - id: "wikipedia" -#hosted: -# homeserverUrl: "https://t2bot.io" -# accessToken: "your_matrix_access_token_here" diff --git a/config/integrations/youtube_widget.yaml b/config/integrations/youtube_widget.yaml deleted file mode 100644 index 36ed516..0000000 --- a/config/integrations/youtube_widget.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# All this configuration does is make "Youtube Widget" available in the UI -type: "widget" -integrationType: "youtube" -enabled: true -name: "YouTube Video" -about: "Embed a YouTube, Vimeo, or DailyMotion video" -avatar: "img/avatars/youtube.png" diff --git a/docs/development/adding_widgets.md b/docs/development/adding_widgets.md deleted file mode 100644 index a839370..0000000 --- a/docs/development/adding_widgets.md +++ /dev/null @@ -1,86 +0,0 @@ -# Adding new widgets to Dimension - -Widgets are small/simple applications that can be loaded via iframes in a Riot.im room. Dimension supports "wrapped widgets" where each widget URL is held in some kind of wrapper to ensure it can make everything work for that widget. For example, Jitsi widgets are wrapped so that the Jitsi elements can be set up. - -To add a new widget to Dimension: - -1. Copy and existing widget's configuration file from `config/integrations` and update it accordingly. The bare minimum is: - ```yaml - # These two options should never change - type: "widget" - enabled: true - - integrationType: "mywidget" # This is the widget type - name: "My Widget" # The human-readable name for the widget - about: "This is a very brief description of the widget" - avatar: "img/avatars/mywidget.png" # Should be a 120x120 PNG image. - # Located at `web/public/img/avatars/mywidget.png` - ``` -2. In `web/app/shared/models/widget.ts` add a constant for your widget. In this example, it would be: - ```typescript - export const WIDGET_MY_WIDGET = ["mywidget"]; - ``` -3. Add a new component to configure your widget under `web/app/configs/widget` (this is best done by copy/pasting another similar widget's component and renaming the files and class name). -4. Register the component in `web/app/shared/integration.service.ts` under the `supportedIntegrationsMap` like so: - ```typescript - private static supportedIntegrationsMap = { - "widget": { - "mywidget": { - component: MyWidgetConfigComponent, - types: WIDGET_MY_WIDGET, - }, - }, - }; - ``` -5. Start building out the widget configuration component. - - -## Widget configuration component methods - -Most widgets should be able to specify their entire behaviour through use of the constructor, however some do require some additional hooks to operate correctly. For example, widgets being imported from older versions of Dimension or other integration managers may have to be massaged into compatible widgets. - -All widgets rely on the `data` property bag to store state information, such as a channel name or conference name. This information is automatically persisted as part of the widget. Whenever you are interacting with a widget in the context of editing or creating it be sure to use the properties under `dimension` on the widget, otherwise your changes may be overwritten. - -Here's a few examples of how to use the hooks supplied by the widget configuration component. For more information on all of the hooks available, please see the bottom of `web/app/configs/widget/widget.component.ts`. - -#### Expanding the widget's data prior to creation - -This is common for when a widget wants to create a new widget based on a channel name (for example). - -```typescript -protected onNewWidgetPrepared() { - this.newWidget.dimension.newData.channelName = ""; -} -``` - -#### Modifying the widget URL (or other properties) before adding/saving - -```typescript -// Make sure to call these methods instead of addWidget and saveWidget in the view! - -public validateAndAddWidget() { - this.newWidget.dimension.newUrl = "https://somewhere.com"; - this.addWidget(); -} - -public validateAndSaveWidget(widget: EditableWidget) { - widget.dimension.newUrl = "https://somewhere.com"; - this.saveWidget(widget); -} -``` - -#### Converting widgets from other data sources (Scalar, older versions, etc) - -```typescript -protected onWidgetsDiscovered() { - for (const widget of this.widgets) { - // Note the use of .data instead of .dimension here. - // This is because we're not editing or creating a widget - just preparing it. - - if (widget.data.oldDataKey) { - widget.data.newDataKey = widget.data.oldDataKey; - } - } -} -``` - diff --git a/docs/dimension_api.md b/docs/dimension_api.md deleted file mode 100644 index aaaa525..0000000 --- a/docs/dimension_api.md +++ /dev/null @@ -1,174 +0,0 @@ -# Dimension API - -Dimension has its own API that allows for management of integrations in Riot/Matrix. - -## Types of integrations - -### Simple Bots - -* Can only be in a room or not -* No state information held - -### Complex Bots - -* Simple Bots that hold state information - -### Bridges - -* Manage their own state through dedicated API endpoints - -## Endpoints - -### `GET /api/v1/dimension/integrations/{roomId}?scalar_token=your_token_here` - -**Parameters** -* `{roomId}` - The room ID to get integrations for -* `scalar_token` - The scalar (dimension) token to authenticate with - -**Example Response** -``` -TODO -``` - -### `DELETE /api/v1/dimension/integrations/{roomId}/{type}/{integrationType}?scalar_token=your_token_here` - -**Parameters** -* `{roomId}` - The room ID to remove the integration from -* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc) -* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc) -* `scalar_token` - The scalar (dimension) token to authenticate with - -**Example Response** -``` -TODO -``` - -### `PUT /api/v1/dimension/integrations/{roomId}/{type}/{integrationType}/state` - -**Parameters** -* `{roomId}` - The room ID to update the integration state in -* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc) -* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc) - -**Example Body** -``` -{ - "scalar_token": "your_token_here", - "state": { - // integration specific state goes here - } -} -``` - -### `GET /api/v1/dimension/integrations/{roomId}/{type}/{integrationType}/state?scalar_token=your_token_here` - -**Parameters** -* `{roomId}` - The room ID to get the integration state in -* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc) -* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc) -* `scalar_token` - The scalar (dimension) token to authenticate with - -**Response** - -An object representing the integration-specific state. See the documentation for the desired integration for more information. - -### `GET /api/v1/dimension/{type}/{integrationType}/oauth/url?redirect=your_url_here&scalar_token=your_token_here` - -**Parameters** -* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc) -* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc) -* `redirect` - The URL to redirect to when complete -* `scalar_token` - The scalar (dimension) token to authenticate with - -**Example Response** -``` -{ - "url": "https://github.com/somewhere?with=params" -} -``` - -### `DELETE /api/v1/dimension/{type}/{integrationType}/oauth?scalar_token=your_token_here` - -**Parameters** -* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc) -* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc) -* `scalar_token` - The scalar (dimension) token to authenticate with - -**Example Response** -``` -{} -``` - -## Integration State Information - -### Simple Bots - -Do not hold state. - -### Complex Bots - -#### RSS Bot -``` -{ - // Mutable using state API - "feeds": [ - "https://some.domain.com/feed.rss", - "https://some.domain.com/another_feed.rss" - ], - - // Read only. Controlled by other users. - "immutableFeeds": [ - "https://some.domain.com/third_feed.rss", - "https://some.domain.com/fourth_feed.rss" - ] -} -``` - -#### Github -``` -{ - // Mutable using state API - "repositories": { - "turt2live/matrix-dimension": ["push", "pull_request", "issues", "issue_comment", "pull_request_review_comment", "labels", "milestones", "assignments"], - "turt2live/matrix-voyager-bot": [] - }, - - // Read only. - "authenticated": true -} -``` - -*Unauthenticated response* -``` -{ - // Read only. - "repositories": {}, - - // Read only. - "authenticated": false -} -``` - -### Bridges - -#### IRC -``` -{ - // Read only - "availableNetworks": [ - {"name": "Freenode", "id": "freenode"}, - {"name": "EsperNet", "id": "espernet"}, - {"name": "OFTC", "id": "oftc"} - ], - - // Read only. Use IRC API to mutate - "channels": { - "freenode": [ - "#dimensiontesting", - "#dimensiontest" - ], - "espernet": [], - "oftc": [] - } -} -``` \ No newline at end of file diff --git a/docs/integrations/irc_bridge.md b/docs/integrations/irc_bridge.md deleted file mode 100644 index 9ea9a0e..0000000 --- a/docs/integrations/irc_bridge.md +++ /dev/null @@ -1,47 +0,0 @@ -# Dimension IRC Bridge API - -As with most bridges, the IRC bridge uses a dedicated set of API endpoints to manage the state of the bridge. The IRC bridge still uses the state API provided by Dimension to report basic state information, but does not allow edits through the regular API. Instead, it is expected that the IRC API be used to mutate the state. - -## Getting available networks/bridged channels - -Make a call to the Dimension state API: `GET /api/v1/dimension/integrations/{roomId}/bridge/irc/state?scalar_token=...`. - -*Example state* -``` -{ - "availableNetworks": [ - {"name": "Freenode", "id": "freenode"}, - {"name": "Espernet", "id": "espernet"}, - {"name": "OFTC", "id": "oftc"} - ], - "channels": { - "freenode": [ - "#dimensiontesting", - "#dimensiontest" - ], - "espernet": [], - "oftc": [] - } -} -``` - -## Getting the OPs in a channel - -IRC API Endpoint: `GET /api/v1/irc/{roomId}/ops/{network}/{channel}?scalar_token=...`. The channel should not include the prefix (`#test` becomes `test`). - -*Example response* -``` -["turt2live", "johndoe"] -``` - -## Linking a new channel - -IRC API Endpoint: `PUT /api/v1/irc/{roomId}/channels/{network}/{channel}?op=turt2live&scalar_token=...`. The channel should not include the prefix (`#test` becomes `test`). - -A 200 OK is returned if the request to add the channel was sent. The channel will not appear in the state information until the op has approved the bridge. - -## Unlinking a channel - -IRC API Endpoint: `DELETE /api/v1/irc/{roomId}/channels/{network}/{channel}?scalar_token=...`. The channel should not include the prefix (`#test` becomes `test`). - -A 200 OK is returned if the delete was successful. \ No newline at end of file diff --git a/docs/riot_widget_api.md b/docs/reference/riot_widget_api.md similarity index 100% rename from docs/riot_widget_api.md rename to docs/reference/riot_widget_api.md diff --git a/docs/scalar_auth.md b/docs/reference/scalar_auth.md similarity index 100% rename from docs/scalar_auth.md rename to docs/reference/scalar_auth.md diff --git a/docs/scalar_bot_options.md b/docs/reference/scalar_bot_options.md similarity index 100% rename from docs/scalar_bot_options.md rename to docs/reference/scalar_bot_options.md diff --git a/docs/scalar_client_api.md b/docs/reference/scalar_client_api.md similarity index 100% rename from docs/scalar_client_api.md rename to docs/reference/scalar_client_api.md diff --git a/docs/scalar_server_api.md b/docs/reference/scalar_server_api.md similarity index 100% rename from docs/scalar_server_api.md rename to docs/reference/scalar_server_api.md diff --git a/jenkins.sh b/jenkins.sh deleted file mode 100644 index c566eaa..0000000 --- a/jenkins.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -npm --version -node --version -npm install -npm run build -rm -f web.zip -zip -r web.zip web-dist/* diff --git a/migrations/20170527035704-add-tokens-table.js b/migrations/20170527035704-add-tokens-table.js deleted file mode 100644 index 34ad73e..0000000 --- a/migrations/20170527035704-add-tokens-table.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var dbm; -var type; -var seed; - -/** - * We receive the dbmigrate dependency from dbmigrate initially. - * This enables us to not have to rely on NODE_PATH. - */ -exports.setup = function (options, seedLink) { - dbm = options.dbmigrate; - type = dbm.dataType; - seed = seedLink; -}; - -exports.up = function (db) { - return db.createTable("tokens", { - id: {type: 'int', primaryKey: true, autoIncrement: true, notNull: true}, - matrixUserId: {type: 'string', notNull: true}, - matrixServerName: {type: 'string', notNull: true}, - matrixAccessToken: {type: 'string', notNull: true}, - scalarToken: {type: 'string', notNull: true}, - expires: {type: 'timestamp', notNull: true} - }); -}; - -exports.down = function (db) { - return db.dropTable("tokens"); -}; - -exports._meta = { - "version": 1 -}; diff --git a/migrations/20170527221910-add-upstream-scalar-token-to-tokens.js b/migrations/20170527221910-add-upstream-scalar-token-to-tokens.js deleted file mode 100644 index 7d025aa..0000000 --- a/migrations/20170527221910-add-upstream-scalar-token-to-tokens.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -var dbm; -var type; -var seed; - -/** - * We receive the dbmigrate dependency from dbmigrate initially. - * This enables us to not have to rely on NODE_PATH. - */ -exports.setup = function (options, seedLink) { - dbm = options.dbmigrate; - type = dbm.dataType; - seed = seedLink; -}; - -exports.up = function (db) { - return db.addColumn('tokens', 'upstreamToken', {type: 'string', notNull: false}); // has to be nullable, despite our best intentions -}; - -exports.down = function (db) { - return db.removeColumn('tokens', 'upstreamToken'); -}; - -exports._meta = { - "version": 1 -}; diff --git a/package-lock.json b/package-lock.json index 9ec711f..deb2654 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,84 +5,84 @@ "requires": true, "dependencies": { "@angular/animations": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.1.0.tgz", - "integrity": "sha512-s0tV6y2D16CQAcXjv8CN8AahHb+LoWm9KAUkxvSJ18ZZQweuAY4T8jlRB95ODRFFKfjwyRD9HqXKUC5yHmG9ww==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.2.9.tgz", + "integrity": "sha512-H/3fMs4PhYjKoA81II6D0PHifDrqlKet2u/EXzUBq3ehXby+N/0GBzqsBYwPeU5pTye7WPFfW+5sgoJpN8Ye6Q==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/common": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.1.0.tgz", - "integrity": "sha512-J6E0OfTJJGcyoKU51ZucsDFV40YEAPgP6VCIPYECgOFHxrqg6O1ZFZSD1fdviMXMLVEFCi6Fy6IB7GJyiWgDIA==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.2.9.tgz", + "integrity": "sha512-g2hPcI0fnT4TV+Fd+1IohjuqBxPvxwyH9IzTn8PkU9X2M+F6cHCUvHxL1sWI2sF8pYcaHzVjq9WClym10X36Lg==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/compiler": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.1.0.tgz", - "integrity": "sha512-iyFQqmhKNRSc9JRx7ty6z/wCsypjpbRu0QR6q2LMa6imuCt9qLHOvTajBQExRB8guqd/LTVDG4WiYY1lf8iO2w==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.2.9.tgz", + "integrity": "sha512-mN+ofInk8y/tk2TCJZx8RrGdOKdrfunoCair7tfDy4XoQJE90waGfaYWo07hYU+UYwLhrg19m2Czy6rIDciUJA==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.1.0.tgz", - "integrity": "sha512-duObjve+INoz4wWuqcaJzl1isUyI37RtRblTFXgZBp2n2n0nXJq1CubcfgxQhMMR2d64xWLKg9+d34PvnzaMmg==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.2.9.tgz", + "integrity": "sha512-cvHBJGtasrIoARvbLFyHaOsiWKVwMNrrSTZLwrlyHP8oYzkDrE0qKGer6QCqyKt+51hF53cgWEffGzM/u/0wYg==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/forms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.1.0.tgz", - "integrity": "sha512-2sJqtMht/6vbFg6HwFs0MX4pRhgLt7h2pa6oTH4oBoQ2UF67jCuq4cMljDm9SVxrGw0Q83+/eBk3ER4QnKk48Q==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.2.9.tgz", + "integrity": "sha512-zyIOiZV/FAm1iVZWTk3Joz6Jt096hbhfDbBUrssmuiTKi9dU6rWG+Z4b88zStqulKe3HFVZkgdixWlminG8nKA==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/http/-/http-5.1.0.tgz", - "integrity": "sha512-ltSs52OYnWZJEnbxtHoN5LQiH/37F3GxN6iL0TsQdSlw8HzrdcdbmebKlCpfXwhgcgZC48KWbKSaOs5/xVurfQ==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-5.2.9.tgz", + "integrity": "sha512-DKjgIk+Dp0Xv1ieG8LawvUnL4dYZp1KroAq5cfKuO9EojP0zM3tUvBtw2vbPLsHYma7g7ZMjOoAbzVxtmTBZqw==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/platform-browser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.1.0.tgz", - "integrity": "sha512-8aeppeASwQv4Fj3B8KBiFHQrKPrwA328AEhlH/HnggCvt0CFffIs2PSqzJBwnOfFWvhFZk020W51B8jrHLQyoQ==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.2.9.tgz", + "integrity": "sha512-P6iviRTuLsLRuqtZNOO0fd4cjTo8DWsDCecwntUlI08R3kH5qeqvqarTzlw/4oD+wBzZY6bfb89JyY+n5XbX3Q==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/platform-browser-dynamic": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.1.0.tgz", - "integrity": "sha512-f6Iv4NCYQwBkNeyInZzja8pg0nfUOrxx5H5rEvr0J1bwag2eDofGVPOftha7LDOLVALVOQQiXQBePATMNLB85g==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.2.9.tgz", + "integrity": "sha512-8C3MtyguJKDTT8FcHIRDlBxswcIdpfugOf4S2t94pVedCr4h9w2da/lcfwJKUISw1aKjfA77Sl8TDUhoS8ymmQ==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angular/router": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-5.1.0.tgz", - "integrity": "sha512-CtOwqeo1IUk4kUs+tUggkYFmuu2fPTZ1G/GP7YK6gd3Jr9OtkMFB7wkmnd5YcaYo3wVeYkJWZdJQAvj6OakMww==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-5.2.9.tgz", + "integrity": "sha512-NtDbFK0EA1rfFc+5Dqd5mIv8E1Wcc5rDUnSty4cX2V+HxTEZvQ9DRdpO2Q0abWU5siXyqponuPHJzF08OVGyNA==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "@angularclass/hmr": { @@ -100,27 +100,165 @@ "loader-utils": "1.1.0" } }, - "@ng-bootstrap/ng-bootstrap": { - "version": "1.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-beta.7.tgz", - "integrity": "sha1-WLyB9hACj0BSZSnOQEg6lQKBY7A=", + "@fortawesome/fontawesome": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome/-/fontawesome-1.1.4.tgz", + "integrity": "sha512-KgmesNO47kpIRBWgy9Tkw62T5stz1PDKn2QNFCuwEuk05zl+NHgy90oZQuww2dXZ24hTOS9YRAx0klE8UBLsxg==", + "dev": true, + "requires": { + "@fortawesome/fontawesome-common-types": "0.1.3" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.1.3.tgz", + "integrity": "sha512-jJXUoNkmHozqlcXzNiYytcmEe3eNxn7Vh8hmnIoRi0TmT3ChQ0osuSApuIObIomnIcvqlA24FCpnamKvfZ7F5g==", "dev": true }, + "@fortawesome/fontawesome-free-brands": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-brands/-/fontawesome-free-brands-5.0.8.tgz", + "integrity": "sha512-r7qD8ZZt4ke0w1ZBHuINNIVHlSQpZOxhToF8CA2PK1TQsg2l/PsM2reTlzkNg/t1EldU4ivXW/qt6Y/uHEMMhw==", + "dev": true, + "requires": { + "@fortawesome/fontawesome-common-types": "0.1.3" + } + }, + "@fortawesome/fontawesome-free-regular": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-regular/-/fontawesome-free-regular-5.0.8.tgz", + "integrity": "sha512-FsLlHx6tKr6sE6Vy8rUEOJI39/UeLebsMGfcsLfvTfq//Y4hjRFJuK1LWpunmOTfT8oxUNvp9fuDPlxO42MEfg==", + "dev": true, + "requires": { + "@fortawesome/fontawesome-common-types": "0.1.3" + } + }, + "@fortawesome/fontawesome-free-solid": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.8.tgz", + "integrity": "sha512-c7cO2YkX+q1jprm/jMq7hzwl46uaeu1T+UF6ecOnvKIvpZbhmA3swomTDgXUXwVTnY17B9hc7XgXLKjzADFxOw==", + "dev": true, + "requires": { + "@fortawesome/fontawesome-common-types": "0.1.3" + } + }, + "@ng-bootstrap/ng-bootstrap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.2.tgz", + "integrity": "sha1-hZoacvfysXZQQL5WbIU03KrQ1w0=", + "dev": true + }, + "@types/bluebird": { + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.18.tgz", + "integrity": "sha512-OTPWHmsyW18BhrnG5x8F7PzeZ2nFxmHGb42bZn79P9hl+GI5cMzyPgQTwNjbem0lJhoru/8vtjAFCUOu3+gE2w==" + }, + "@types/body-parser": { + "version": "1.16.8", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.8.tgz", + "integrity": "sha512-BdN2PXxOFnTXFcyONPW6t0fHjz2fvRZHVMFpaS0wYr+Y8fWEaNOs4V8LEu/fpzQlMx+ahdndgTaGTwPC+J/EeA==", + "requires": { + "@types/express": "4.0.39", + "@types/node": "8.9.5" + } + }, + "@types/continuation-local-storage": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha1-oz4N+dzptCTRyY/E/evYV43O7H4=", + "requires": { + "@types/node": "8.9.5" + } + }, + "@types/cookie-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.1.tgz", + "integrity": "sha512-iJY6B3ZGufLiDf2OCAgiAAQuj1sMKC/wz/7XCEjZ+/MDuultfFJuSwrBKcLSmJ5iYApLzCCYBYJZs0Ws8GPmwA==", + "requires": { + "@types/express": "4.0.39" + } + }, + "@types/express": { + "version": "4.0.39", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.39.tgz", + "integrity": "sha512-dBUam7jEjyuEofigUXCtublUHknRZvcRgITlGsTbFgPvnTwtQUt2NgLakbsf+PsGo/Nupqr3IXCYsOpBpofyrA==", + "requires": { + "@types/body-parser": "1.16.8", + "@types/express-serve-static-core": "4.0.57", + "@types/serve-static": "1.13.1" + } + }, + "@types/express-serve-static-core": { + "version": "4.0.57", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.57.tgz", + "integrity": "sha512-QLAHjdLwEICm3thVbXSKRoisjfgMVI4xJH/HU8F385BR2HI7PmM6ax4ELXf8Du6sLmSpySXMYaI+xc//oQ/IFw==", + "requires": { + "@types/node": "8.9.5" + } + }, "@types/geojson": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" }, "@types/jquery": { - "version": "3.2.16", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.16.tgz", - "integrity": "sha512-q2WC02YxQoX2nY1HRKlYGHpGP1saPmD7GN0pwCDlTz35a4eOtJG+aHRlXyjCuXokUukSrR2aXyBhSW3j+jPc0A==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-N3h+rzN518yl2xKrW0o6KKdNmWZ+OwG6SoM5TBEQFF0tTv5wXPEsoOuYQ2Kt3/89XbcSZUJLdjiT/2c3BR/ApQ==", "dev": true }, + "@types/lodash": { + "version": "4.14.105", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.105.tgz", + "integrity": "sha512-LB5PKR4QNoDrgcl4H8JdhBMp9wHWp0OATkU9EHzuXKiutRwbvsyYmqPUaMSWmdCycJoKHtdAWh47/zSe/GZ1yA==" + }, + "@types/mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==" + }, + "@types/multer": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-0.0.32.tgz", + "integrity": "sha1-+Jx1EifcILfJM8MJo+dGfEmfzew=", + "requires": { + "@types/express": "4.0.39" + } + }, "@types/node": { - "version": "6.0.92", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.92.tgz", - "integrity": "sha512-awEYSSTn7dauwVCYSx2CJaPTu0Z1Ht2oR1b2AD3CYao6ZRb+opb6EL43fzmD7eMFgMHzTBWSUzlWSD+S8xN0Nw==" + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", + "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==" + }, + "@types/reflect-metadata": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/reflect-metadata/-/reflect-metadata-0.0.4.tgz", + "integrity": "sha1-tkd8qal+UmXyrGf56nBOrl4Or00=" + }, + "@types/sequelize": { + "version": "4.27.5", + "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.27.5.tgz", + "integrity": "sha512-K2xXW0IYWZj2VX4DxyHQERxrWHNyZuSOERyo+wcZOgWUHzjICwrwcusvSnO4aIiCXqPA2NDjqbb0o0GrVzIqVA==", + "requires": { + "@types/bluebird": "3.5.18", + "@types/continuation-local-storage": "3.2.1", + "@types/lodash": "4.14.105", + "@types/validator": "9.4.0" + } + }, + "@types/serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-jDMH+3BQPtvqZVIcsH700Dfi8Q3MIcEx16g/VdxjoqiGR/NntekB10xdBpirMKnPe9z2C5cBmL0vte0YttOr3Q==", + "requires": { + "@types/express-serve-static-core": "4.0.57", + "@types/mime": "2.0.0" + } + }, + "@types/validator": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-9.4.0.tgz", + "integrity": "sha512-SxPH9AKnmr9I2V4CnPgG9BEUEJ6wUUoEZt4hVcCKUM143qMHQgU8ewLQO5WKSjgQqVyTmVufmouY1z0cKaOFhQ==" }, "abbrev": { "version": "1.1.1", @@ -129,18 +267,33 @@ "dev": true }, "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "requires": { - "mime-types": "2.1.17", + "mime-types": "2.1.18", "negotiator": "0.6.1" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + } } }, "acorn": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", - "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", "dev": true }, "acorn-dynamic-import": { @@ -161,20 +314,20 @@ } }, "ajv": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.1.tgz", - "integrity": "sha1-s4u4h22ehr7plJVqBOch6IskjrI=", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.3.0.tgz", + "integrity": "sha1-FlCkERTvAFdMrBC4Ay2PTBSBLac=", + "dev": true, "requires": { - "co": "4.6.0", "fast-deep-equal": "1.0.0", "fast-json-stable-stringify": "2.0.0", "json-schema-traverse": "0.3.1" } }, "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", + "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=", "dev": true }, "align-text": { @@ -194,7 +347,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -241,9 +394,9 @@ } }, "angular2-toaster": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/angular2-toaster/-/angular2-toaster-4.0.1.tgz", - "integrity": "sha1-WHrvbfkDz14rfrpAR7VOyaIbjjs=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/angular2-toaster/-/angular2-toaster-4.0.2.tgz", + "integrity": "sha512-/ndYYbV/7WZx6ujm6avFUqfb+FKbrx7oT+3mYj8i0o9N26Ug+BseFjy6oRnlVVedl39yRP6hhea81QgKmoYbbQ==", "dev": true }, "angular2-ui-switch": { @@ -278,104 +431,13 @@ } }, "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "2.3.11", + "micromatch": "3.1.9", "normalize-path": "2.1.1" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - } } }, "app-root-path": { @@ -384,6 +446,11 @@ "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", "dev": true }, + "append-field": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz", + "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo=" + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -397,7 +464,7 @@ "dev": true, "requires": { "delegates": "1.0.0", - "readable-stream": "2.3.3" + "readable-stream": "2.3.5" } }, "argparse": { @@ -444,14 +511,13 @@ "dev": true, "requires": { "define-properties": "1.1.2", - "es-abstract": "1.10.0" + "es-abstract": "1.11.0" } }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, "requires": { "array-uniq": "1.0.3" } @@ -459,8 +525,7 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { "version": "0.3.2", @@ -468,15 +533,21 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "asn1.js": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", - "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "requires": { "bn.js": "4.11.8", @@ -498,6 +569,12 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, "ast-types": { "version": "0.9.6", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", @@ -505,9 +582,13 @@ "dev": true }, "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } }, "async-each": { "version": "1.0.1", @@ -547,19 +628,55 @@ } }, "awesome-typescript-loader": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-3.4.1.tgz", - "integrity": "sha512-fYxBtN6s4Dm6vtsROWi8IQ4I+KcmwRWePAVvhI06mFcHbtHfZopOs4qGNu9LyCPEw403LDROKFA+NVV6ig5yNw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-3.5.0.tgz", + "integrity": "sha512-qzgm9SEvodVkSi9QY7Me1/rujg+YBNMjayNSAyzNghwTEez++gXoPCwMvpbHRG7wrOkDCiF6dquvv9ESmUBAuw==", "dev": true, "requires": { - "colors": "1.1.2", + "chalk": "2.3.2", "enhanced-resolve": "3.3.0", "loader-utils": "1.1.0", - "lodash": "4.17.4", - "micromatch": "3.1.4", + "lodash": "4.17.5", + "micromatch": "3.1.9", "mkdirp": "0.5.1", - "object-assign": "4.1.1", - "source-map-support": "0.4.18" + "source-map-support": "0.5.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } } }, "aws-sign2": { @@ -614,16 +731,16 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { - "core-js": "2.5.2", + "core-js": "2.5.3", "regenerator-runtime": "0.11.0" } }, "balanced-match": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true }, "base": { "version": "0.11.2", @@ -632,18 +749,29 @@ "dev": true, "requires": { "cache-base": "1.0.1", - "class-utils": "0.3.5", + "class-utils": "0.3.6", "component-emitter": "1.2.1", "define-property": "1.0.0", "isobject": "3.0.1", - "mixin-deep": "1.3.0", + "mixin-deep": "1.3.1", "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + } } }, "base64-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", + "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==", "dev": true }, "batch": { @@ -727,7 +855,7 @@ "deep-equal": "1.0.1", "dns-equal": "1.0.0", "dns-txt": "2.0.2", - "multicast-dns": "6.2.1", + "multicast-dns": "6.2.3", "multicast-dns-service-types": "1.1.0" }, "dependencies": { @@ -736,12 +864,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", "dev": true - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true } } }, @@ -756,7 +878,7 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, "brace-expansion": { @@ -776,9 +898,9 @@ } }, "braces": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.0.tgz", - "integrity": "sha512-P4O8UQRdGiMLWSizsApmXVQDBS6KCt7dSexgLKBmH5Hr1CZq7vsnscFh8oR1sP1ab1Zj0uCHCEzZeV6SfUf3rA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", + "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", "dev": true, "requires": { "arr-flatten": "1.1.0", @@ -787,11 +909,32 @@ "extend-shallow": "2.0.1", "fill-range": "4.0.0", "isobject": "3.0.1", + "kind-of": "6.0.2", "repeat-element": "1.1.2", - "snapdragon": "0.8.1", + "snapdragon": "0.8.2", "snapdragon-node": "2.1.1", "split-string": "3.1.0", - "to-regex": "3.0.1" + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } } }, "brorand": { @@ -848,7 +991,7 @@ "dev": true, "requires": { "bn.js": "4.11.8", - "randombytes": "2.0.5" + "randombytes": "2.0.6" } }, "browserify-sign": { @@ -891,11 +1034,16 @@ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "1.2.1", - "ieee754": "1.1.8", + "base64-js": "1.2.3", + "ieee754": "1.1.10", "isarray": "1.0.0" } }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" + }, "buffer-indexof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", @@ -920,11 +1068,64 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.14" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "3.5.1", + "chownr": "1.0.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lru-cache": "4.1.2", + "mississippi": "2.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.2", + "ssri": "5.3.0", + "unique-filename": "1.1.0", + "y18n": "4.0.0" + } + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -942,6 +1143,11 @@ "unset-value": "1.0.0" } }, + "callsites": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-1.0.1.tgz", + "integrity": "sha1-wUwkGIzo4dagMLTDyULmuolbaho=" + }, "camel-case": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", @@ -987,9 +1193,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000718.tgz", - "integrity": "sha1-DdJCkL6xExCy2A9rcKgjwqZab60=", + "version": "1.0.30000817", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000817.tgz", + "integrity": "sha512-O68CSiZY4NuxdGT0gQO+2vtU07/FOrCUpGPYincrOjybQIdsSyWC9CWx+hhm+SnCTAwpG/AywH4cU5RAkqbFXA==", "dev": true }, "caseless": { @@ -1005,14 +1211,6 @@ "requires": { "align-text": "0.1.4", "lazy-cache": "1.0.4" - }, - "dependencies": { - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - } } }, "chalk": { @@ -1026,38 +1224,30 @@ } }, "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.2.tgz", + "integrity": "sha512-l32Hw3wqB0L2kGVmSbK/a+xXLDrUEsc84pSgMkmwygHvD7ubRsP/vxxHa5BtB6oix1XLLVCHyYMsckRXxThmZw==", "dev": true, "requires": { - "anymatch": "1.3.2", + "anymatch": "2.0.0", "async-each": "1.0.1", - "glob-parent": "2.0.0", + "braces": "2.3.1", + "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", - "is-glob": "2.0.1", + "is-glob": "4.0.0", + "normalize-path": "2.1.1", "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - } + "readdirp": "2.1.0", + "upath": "1.0.4" } }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "dev": true + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -1105,15 +1295,14 @@ } }, "class-utils": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.5.tgz", - "integrity": "sha1-F+eTEDdQ+WJ7IXbqNM/RtWWQPIA=", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { "arr-union": "3.1.0", "define-property": "0.2.5", "isobject": "3.0.1", - "lazy-cache": "2.0.2", "static-extend": "0.1.2" }, "dependencies": { @@ -1126,6 +1315,46 @@ "is-descriptor": "0.1.6" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -1172,26 +1401,15 @@ "dev": true }, "clone-deep": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz", - "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", "dev": true, "requires": { "for-own": "1.0.0", "is-plain-object": "2.0.4", - "kind-of": "3.2.2", - "shallow-clone": "0.1.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } + "kind-of": "6.0.2", + "shallow-clone": "1.0.0" } }, "cls-bluebird": { @@ -1224,9 +1442,9 @@ "dev": true }, "codelyzer": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-3.2.2.tgz", - "integrity": "sha512-VNvW9gRThsqRarEnLioiILd0Pdk0yCq/7cVgYvqHpC+3CHqfnrJfmXjoana7vzWfSis+9pODXofjCWX+nlU9Gw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.2.1.tgz", + "integrity": "sha512-CKwfgpfkqi9dyzy4s6ELaxJ54QgJ6A8iTSsM4bzHbLuTpbKncvNc3DUlCvpnkHBhK47gEf4qFsWoYqLrJPhy6g==", "dev": true, "requires": { "app-root-path": "2.0.1", @@ -1294,12 +1512,13 @@ "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { "delayed-stream": "1.0.0" } @@ -1310,6 +1529,12 @@ "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -1317,23 +1542,31 @@ "dev": true }, "compressible": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.12.tgz", - "integrity": "sha1-xZpcmdt2dn6YdlAOJx72OzSTvWY=", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", + "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", "dev": true, "requires": { - "mime-db": "1.30.0" + "mime-db": "1.33.0" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + } } }, "compression": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.1.tgz", - "integrity": "sha1-7/JgPvwuIs+G810uuTWJ+YdTc9s=", + "version": "1.7.2", + "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz", + "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", "dev": true, "requires": { - "accepts": "1.3.4", + "accepts": "1.3.5", "bytes": "3.0.0", - "compressible": "2.0.12", + "compressible": "2.0.13", "debug": "2.6.9", "on-headers": "1.0.1", "safe-buffer": "5.1.1", @@ -1345,10 +1578,21 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.5", + "typedarray": "0.0.6" + } + }, "config": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/config/-/config-1.28.1.tgz", - "integrity": "sha1-diXSoeTJDxMdinM0eYLZPDhzKC0=", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/config/-/config-1.30.0.tgz", + "integrity": "sha1-HWCp81NIoTwXV5jThOgaWhbDum4=", "requires": { "json5": "0.4.0", "os-homedir": "1.0.2" @@ -1396,11 +1640,34 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "1.2.0", + "fs-write-stream-atomic": "1.0.10", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" + } + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -1408,50 +1675,40 @@ "dev": true }, "copy-webpack-plugin": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.2.3.tgz", - "integrity": "sha512-cL/Wl3Y1QmmKThl/mWeGB+HH3YH+25tn8nhqEGsZda4Yn7GqGnDZ+TbeKJ7A6zvrxyNhhuviYAxn/tCyyAqh8Q==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.1.tgz", + "integrity": "sha512-OlTo6DYg0XfTKOF8eLf79wcHm4Ut10xU2cRBRPMW/NA5F9VMjZGTfRHWDIYC3s+1kObGYrBLshXWU1K0hILkNQ==", "dev": true, "requires": { - "bluebird": "3.5.1", - "glob": "7.1.2", + "cacache": "10.0.4", + "find-cache-dir": "1.0.0", + "globby": "7.1.1", "is-glob": "4.0.0", - "loader-utils": "0.2.17", - "lodash": "4.17.4", - "minimatch": "3.0.4" - }, - "dependencies": { - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" - } - } + "loader-utils": "1.1.0", + "minimatch": "3.0.4", + "p-limit": "1.2.0", + "serialize-javascript": "1.4.0" } }, "core-js": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.2.tgz", - "integrity": "sha1-vEZIZW59ydyA19PHu8Fy2W50TmM=", - "dev": true + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, "cosmiconfig": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", @@ -1459,7 +1716,7 @@ "dev": true, "requires": { "is-directory": "0.3.1", - "js-yaml": "3.10.0", + "js-yaml": "3.11.0", "minimist": "1.2.0", "object-assign": "4.1.1", "os-homedir": "1.0.2", @@ -1494,7 +1751,7 @@ "cipher-base": "1.0.4", "inherits": "2.0.3", "ripemd160": "2.0.1", - "sha.js": "2.4.9" + "sha.js": "2.4.11" } }, "create-hmac": { @@ -1508,7 +1765,7 @@ "inherits": "2.0.3", "ripemd160": "2.0.1", "safe-buffer": "5.1.1", - "sha.js": "2.4.9" + "sha.js": "2.4.11" } }, "cross-spawn": { @@ -1517,7 +1774,7 @@ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "dev": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "which": "1.3.0" } }, @@ -1534,7 +1791,7 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } } } @@ -1554,19 +1811,19 @@ "inherits": "2.0.3", "pbkdf2": "3.0.14", "public-encrypt": "4.0.0", - "randombytes": "2.0.5", - "randomfill": "1.0.3" + "randombytes": "2.0.6", + "randomfill": "1.0.4" } }, "css-color-function": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/css-color-function/-/css-color-function-1.3.0.tgz", - "integrity": "sha1-csdnuvl48BuKipT0Lxe6XSKndvw=", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/css-color-function/-/css-color-function-1.3.3.tgz", + "integrity": "sha1-jtJMLAIFBzM5+voAS8jBQfzLKC4=", "dev": true, "requires": { "balanced-match": "0.1.0", "color": "0.11.4", - "debug": "0.7.4", + "debug": "3.1.0", "rgb": "0.1.0" }, "dependencies": { @@ -1577,10 +1834,13 @@ "dev": true }, "debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", - "dev": true + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } } } }, @@ -1591,9 +1851,9 @@ "dev": true }, "css-loader": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.7.tgz", - "integrity": "sha512-GxMpax8a/VgcfRrVy0gXD6yLd5ePYbXX/5zGgTVYp4wXtJklS8Z2VaUArJgc//f6/Dzil7BaJObdSv8eKKCPgg==", + "version": "0.28.11", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", + "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", "dev": true, "requires": { "babel-code-frame": "6.26.0", @@ -1604,7 +1864,7 @@ "lodash.camelcase": "4.3.0", "object-assign": "4.1.1", "postcss": "5.2.17", - "postcss-modules-extract-imports": "1.1.0", + "postcss-modules-extract-imports": "1.2.0", "postcss-modules-local-by-default": "1.2.0", "postcss-modules-scope": "1.1.0", "postcss-modules-values": "1.3.0", @@ -1726,13 +1986,19 @@ "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.37" + "es5-ext": "0.10.41" } }, "dashdash": { @@ -1749,53 +2015,6 @@ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", "dev": true }, - "db-migrate": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/db-migrate/-/db-migrate-0.10.2.tgz", - "integrity": "sha512-uU3AXQ89DPKAePCHHoZSJRnLam6/zgOBhgSXVXB9Z3JeHms5OMTQuCdj1leUwYvI6ygHdQ3Z9CS6S9yOuELdcw==", - "requires": { - "balanced-match": "0.4.2", - "bluebird": "3.5.1", - "db-migrate-shared": "1.1.3", - "deep-extend": "0.4.2", - "dotenv": "2.0.0", - "final-fs": "1.6.1", - "inflection": "1.12.0", - "mkdirp": "0.5.1", - "moment": "2.19.3", - "optimist": "0.6.1", - "parse-database-url": "0.3.0", - "pkginfo": "0.4.1", - "prompt": "1.0.0", - "rc": "1.2.2", - "resolve": "1.4.0", - "semver": "5.4.1", - "tunnel-ssh": "4.1.3" - } - }, - "db-migrate-base": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/db-migrate-base/-/db-migrate-base-1.5.3.tgz", - "integrity": "sha512-dbJHFVYIY75vSOL3MD4qbpjilcwrYn7ZnTqsdA2AGs6w1mYugspfWEBea+uMgJVH/YsUFxw2AYPKRHCHUjhirw==", - "requires": { - "bluebird": "3.5.1" - } - }, - "db-migrate-shared": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/db-migrate-shared/-/db-migrate-shared-1.1.3.tgz", - "integrity": "sha1-7bPIF7KVAixhmEwYcmdBm7NqLVg=" - }, - "db-migrate-sqlite3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/db-migrate-sqlite3/-/db-migrate-sqlite3-0.2.1.tgz", - "integrity": "sha1-ox1nudNtz5lWSymLz9C5hx+2bmc=", - "requires": { - "bluebird": "3.5.1", - "db-migrate-base": "1.5.3", - "sqlite3": "3.1.13" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1817,14 +2036,10 @@ "dev": true }, "deep-equal": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", - "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=" - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true }, "define-properties": { "version": "1.1.2", @@ -1837,12 +2052,13 @@ } }, "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "1.0.1" + "is-descriptor": "1.0.2", + "isobject": "3.0.1" } }, "defined": { @@ -1865,11 +2081,26 @@ "rimraf": "2.6.2" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } } } }, @@ -1910,10 +2141,42 @@ "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", "dev": true }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.14", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "diff": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", - "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "diffie-hellman": { @@ -1924,7 +2187,17 @@ "requires": { "bn.js": "4.11.8", "miller-rabin": "4.0.1", - "randombytes": "2.0.5" + "randombytes": "2.0.6" + } + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "path-type": "3.0.0" } }, "dns-equal": { @@ -1934,9 +2207,9 @@ "dev": true }, "dns-packet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.2.2.tgz", - "integrity": "sha512-kN+DjfGF7dJGUL7nWRktL9Z18t1rWP3aQlyZdY8XlpvU3Nc6GeFTQApftcjtWKxAZfiggZSGrCEoszNgvnpwDg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", "dev": true, "requires": { "ip": "1.1.5", @@ -1996,9 +2269,9 @@ } }, "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, "domelementtype": { @@ -2026,16 +2299,23 @@ "domelementtype": "1.3.0" } }, - "dotenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-2.0.0.tgz", - "integrity": "sha1-vXWcNXqqcDZeAclrewvsCKbg2Uk=" - }, "dottie": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz", "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA=" }, + "duplexify": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", + "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.5", + "stream-shift": "1.0.0" + } + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -2075,6 +2355,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/embed-video/-/embed-video-2.0.0.tgz", "integrity": "sha1-1/JouzRkIg9pXbM6YCHhpgjI4fk=", + "dev": true, "requires": { "fetch-ponyfill": "4.1.0", "lodash.escape": "4.0.1", @@ -2088,18 +2369,28 @@ "dev": true }, "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, "requires": { "iconv-lite": "0.4.15" } }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, "enhanced-resolve": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz", @@ -2119,12 +2410,12 @@ "dev": true }, "errno": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", - "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { - "prr": "0.0.0" + "prr": "1.0.1" } }, "error-ex": { @@ -2137,9 +2428,9 @@ } }, "es-abstract": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", - "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", + "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", "dev": true, "requires": { "es-to-primitive": "1.1.1", @@ -2169,13 +2460,14 @@ } }, "es5-ext": { - "version": "0.10.37", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", - "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", + "version": "0.10.41", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.41.tgz", + "integrity": "sha512-MYK02wXfwTMie5TEJWPolgOsXEmz7wKCQaGzgmRjZOoV6VLG8I5dSv2bn6AOClXhK64gnSQTQ9W9MKvx87J4gw==", "dev": true, "requires": { "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" } }, "es6-iterator": { @@ -2185,7 +2477,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.41", "es6-symbol": "3.1.1" } }, @@ -2196,7 +2488,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.41", "es6-iterator": "2.0.3", "es6-set": "0.1.5", "es6-symbol": "3.1.1", @@ -2210,12 +2502,17 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.41", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", "event-emitter": "0.3.5" } }, + "es6-shim": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.3.tgz", + "integrity": "sha1-m/tzY/7//4emzbbNk+QF7DxLbyY=" + }, "es6-symbol": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", @@ -2223,7 +2520,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.41" } }, "es6-templates": { @@ -2243,7 +2540,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.41", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } @@ -2266,23 +2563,23 @@ "requires": { "es6-map": "0.1.5", "es6-weak-map": "2.0.2", - "esrecurse": "4.2.0", + "esrecurse": "4.2.1", "estraverse": "4.2.0" } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" + "estraverse": "4.2.0" } }, "estraverse": { @@ -2309,7 +2606,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.41" } }, "eventemitter3": { @@ -2364,7 +2661,7 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "shebang-command": "1.2.0", "which": "1.3.0" } @@ -2381,9 +2678,9 @@ "define-property": "0.2.5", "extend-shallow": "2.0.1", "posix-character-classes": "0.1.1", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -2395,6 +2692,55 @@ "is-descriptor": "0.1.6" } }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -2460,17 +2806,17 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } }, "express": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", - "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "requires": { - "accepts": "1.3.4", + "accepts": "1.3.5", "array-flatten": "1.1.1", "body-parser": "1.18.2", "content-disposition": "0.5.2", @@ -2478,39 +2824,61 @@ "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.1", - "encodeurl": "1.0.1", + "depd": "1.1.2", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "etag": "1.8.1", - "finalhandler": "1.1.0", + "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "1.1.2", "on-finished": "2.3.0", "parseurl": "1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.2", + "proxy-addr": "2.0.3", "qs": "6.5.1", "range-parser": "1.2.0", "safe-buffer": "5.1.1", - "send": "0.16.1", - "serve-static": "1.13.1", + "send": "0.16.2", + "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.15", + "statuses": "1.4.0", + "type-is": "1.6.16", "utils-merge": "1.0.1", "vary": "1.1.2" }, "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.18" + } } } }, @@ -2520,18 +2888,30 @@ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } } }, "extglob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.2.tgz", - "integrity": "sha512-I0+eZBH+jFGL8F5BnIz2ON2nKCjTS3AS3H/5PeSmCp7UVC70Ym8IhdRiQly2juKYQ//f7z1aj1BRpQniFJoU1w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { "array-unique": "0.3.2", @@ -2539,9 +2919,29 @@ "expand-brackets": "2.1.4", "extend-shallow": "2.0.1", "fragment-cache": "0.2.1", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } } }, "extract-text-webpack-plugin": { @@ -2562,7 +2962,7 @@ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "4.17.5" } } } @@ -2606,18 +3006,31 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz", "integrity": "sha1-rjzl9zLGReq4fkroeTQUcJsjmJM=", + "dev": true, "requires": { "node-fetch": "1.7.3" } }, "file-loader": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.5.tgz", - "integrity": "sha512-RzGHDatcVNpGISTvCpfUfOGpYuSR7HSsSg87ki+wF6rw1Hm0RALPTiAdsxAq1UwLf0RRhbe22/eHK6nhXspiOQ==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", "dev": true, "requires": { "loader-utils": "1.1.0", - "schema-utils": "0.3.0" + "schema-utils": "0.4.5" + }, + "dependencies": { + "schema-utils": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", + "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", + "dev": true, + "requires": { + "ajv": "6.3.0", + "ajv-keywords": "3.1.0" + } + } } }, "filename-regex": { @@ -2636,46 +3049,51 @@ "is-number": "3.0.0", "repeat-string": "1.6.1", "to-regex-range": "2.1.1" - } - }, - "final-fs": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/final-fs/-/final-fs-1.6.1.tgz", - "integrity": "sha1-1tzZLvb+T+jAer1WjHE1YQ7eMjY=", - "requires": { - "node-fs": "0.1.7", - "when": "2.0.1" - } - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" }, "dependencies": { - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } } } }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.4.0", + "unpipe": "1.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "commondir": "1.0.1", + "make-dir": "1.2.0", + "pkg-dir": "2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" } }, "flatten": { @@ -2684,6 +3102,16 @@ "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", "dev": true }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -2711,12 +3139,12 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "mime-types": "2.1.17" } }, @@ -2739,6 +3167,37 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, + "fs-extra": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz", + "integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "2.4.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "2.3.5" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2803,9 +3262,9 @@ } }, "generic-pool": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.2.0.tgz", - "integrity": "sha512-JjcXDHT84icN/kFaF5+rNd1trZsgJFVqTSgM9dv6eayxSIQKMq0ilBJ+5pvf0SgimacMlZEsav4oL+4dUE4E2g==" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", + "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==" }, "get-caller-file": { "version": "1.0.2", @@ -2839,6 +3298,28 @@ "assert-plus": "1.0.0" } }, + "git-rev-sync": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/git-rev-sync/-/git-rev-sync-1.10.0.tgz", + "integrity": "sha1-6KxP0JZyRJFI/2lOLXxqbuNck6Y=", + "requires": { + "escape-string-regexp": "1.0.5", + "graceful-fs": "4.1.11", + "shelljs": "0.7.7" + }, + "dependencies": { + "shelljs": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz", + "integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=", + "requires": { + "glob": "7.1.2", + "interpret": "1.0.3", + "rechoir": "0.6.2" + } + } + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -2862,6 +3343,15 @@ "is-glob": "2.0.1" }, "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", @@ -2880,42 +3370,36 @@ } }, "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "requires": { - "is-glob": "2.0.1" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "requires": { - "is-extglob": "1.0.0" + "is-extglob": "2.1.1" } } } }, "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { "array-union": "1.0.2", + "dir-glob": "2.0.0", "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "ignore": "3.3.7", + "pify": "3.0.0", + "slash": "1.0.0" } }, "globule": { @@ -2925,7 +3409,7 @@ "dev": true, "requires": { "glob": "7.1.2", - "lodash": "4.17.4", + "lodash": "4.17.5", "minimatch": "3.0.4" } }, @@ -2941,8 +3425,7 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "handle-thing": { "version": "1.2.5", @@ -2960,8 +3443,21 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.1", + "ajv": "5.5.2", "har-schema": "2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + } } }, "has": { @@ -3020,7 +3516,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -3051,7 +3547,7 @@ "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", - "hoek": "4.2.0", + "hoek": "4.2.1", "sntp": "2.1.0" } }, @@ -3073,14 +3569,14 @@ } }, "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" }, "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", "dev": true }, "hpack.js": { @@ -3090,9 +3586,9 @@ "dev": true, "requires": { "inherits": "2.0.3", - "obuf": "1.1.1", - "readable-stream": "2.3.3", - "wbuf": "1.7.2" + "obuf": "1.1.2", + "readable-stream": "2.3.5", + "wbuf": "1.7.3" } }, "html-comment-regex": { @@ -3108,16 +3604,56 @@ "dev": true }, "html-loader": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-0.5.1.tgz", - "integrity": "sha512-RxokXoxcsRSWcN553Ew+K0TUo68gQfmddTuUIZ4xRD8Ax1xXzX2UYQ3FC3D5MoRPGAdL1erWKeEFihDrrdxHiA==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-0.5.5.tgz", + "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", "dev": true, "requires": { "es6-templates": "0.2.3", "fastparse": "1.1.1", - "html-minifier": "3.5.3", + "html-minifier": "3.5.12", "loader-utils": "1.1.0", "object-assign": "4.1.1" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "html-minifier": { + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.12.tgz", + "integrity": "sha512-+N778qLf0RWBscD0TPGoYdeGNDZ0s76/0pQhY1/409EOudcENkm9IbSkk37RDyPdg/09GVHTKotU4ya93RF1Gg==", + "dev": true, + "requires": { + "camel-case": "3.0.0", + "clean-css": "4.1.7", + "commander": "2.15.1", + "he": "1.1.1", + "ncname": "1.0.0", + "param-case": "2.1.1", + "relateurl": "0.2.7", + "uglify-js": "3.3.16" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.16.tgz", + "integrity": "sha512-FMh5SRqJRGhv9BbaTffENIpDDQIoPDR8DBraunGORGhySArsXlw9++CN+BWzPBLpoI4RcSnpfGPnilTxWL3Vvg==", + "dev": true, + "requires": { + "commander": "2.15.1", + "source-map": "0.6.1" + } + } } }, "html-minifier": { @@ -3145,7 +3681,7 @@ "bluebird": "3.5.1", "html-minifier": "3.5.3", "loader-utils": "0.2.17", - "lodash": "4.17.4", + "lodash": "4.17.5", "pretty-error": "2.1.1", "toposort": "1.0.3" }, @@ -3235,9 +3771,9 @@ } }, "http-parser-js": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.9.tgz", - "integrity": "sha1-6hoE+2St/wJC6ZdPKX3Uw8rSceE=", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.11.tgz", + "integrity": "sha512-QCR5O2AjjMW8Mo4HyI1ctFcv+O99j/0g367V3YoVnrNw5hkDvAWZD0lWGcc+F4yN3V55USPCVix4efb75HxFfA==", "dev": true }, "http-proxy": { @@ -3258,7 +3794,7 @@ "requires": { "http-proxy": "1.16.2", "is-glob": "3.1.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "micromatch": "2.3.11" }, "dependencies": { @@ -3329,7 +3865,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } }, "micromatch": { @@ -3379,7 +3915,7 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" } }, "https-browserify": { @@ -3388,15 +3924,11 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "i": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", - "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=" - }, "iconv-lite": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", + "dev": true }, "icss-replace-symbols": { "version": "1.1.0", @@ -3410,18 +3942,44 @@ "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", "dev": true, "requires": { - "postcss": "6.0.14" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", "source-map": "0.6.1", - "supports-color": "4.5.0" + "supports-color": "5.3.0" } }, "source-map": { @@ -3431,32 +3989,50 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } }, "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.10.tgz", + "integrity": "sha512-byWFX8OyW/qeVxcY21r6Ncxl0ZYHgnf0cPup2h34eHXrCJbOp7IuqnJ4Q0omfyWl6Z++BTI6bByf31pZt7iRLg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", "dev": true }, "import-local": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-0.1.1.tgz", - "integrity": "sha1-sReVcqrNwRxqkQCftDDbyrX2aKg=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", "dev": true, "requires": { "pkg-dir": "2.0.0", "resolve-cwd": "2.0.0" } }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, "in-publish": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", @@ -3503,11 +4079,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "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==" - }, "internal-ip": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", @@ -3520,8 +4091,7 @@ "interpret": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", - "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", - "dev": true + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=" }, "invert-kv": { "version": "1.0.0", @@ -3536,9 +4106,9 @@ "dev": true }, "ipaddr.js": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", - "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" }, "is-absolute-url": { "version": "2.1.0", @@ -3547,23 +4117,12 @@ "dev": true }, "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } + "kind-of": "6.0.2" } }, "is-arrayish": { @@ -3587,9 +4146,9 @@ "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" }, "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-builtin-module": { @@ -3608,23 +4167,12 @@ "dev": true }, "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } + "kind-of": "6.0.2" } }, "is-date-object": { @@ -3634,22 +4182,14 @@ "dev": true }, "is-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.1.tgz", - "integrity": "sha512-G3fFVFTqfaqu7r4YuSBHKBAuOaLz8Sy7ekklUpFEliaLMP1Y2ZjoN9jS62YWCAPQrQpMUQSitRlrzibbuCZjdA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-directory": { @@ -3682,8 +4222,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { "version": "1.0.2", @@ -3712,14 +4251,21 @@ "is-extglob": "2.1.1" } }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, "is-my-json-valid": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", - "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", "dev": true, "requires": { "generate-function": "2.0.0", "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", "jsonpointer": "4.0.1", "xtend": "4.0.1" } @@ -3739,18 +4285,26 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } }, "is-odd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-1.0.0.tgz", - "integrity": "sha1-O4qTLrAos3dcObsJ6RdnrM22kIg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", "dev": true, "requires": { - "is-number": "3.0.0" + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } } }, "is-path-cwd": { @@ -3822,7 +4376,8 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-svg": { "version": "2.1.0", @@ -3850,6 +4405,12 @@ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", @@ -3859,8 +4420,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -3886,9 +4446,9 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jquery": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz", - "integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==", "dev": true }, "js-base64": { @@ -3904,12 +4464,19 @@ "dev": true }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "requires": { "argparse": "1.0.9", "esprima": "4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + } } }, "jsbn": { @@ -3965,6 +4532,14 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=" }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "4.1.11" + } + }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -4001,13 +4576,10 @@ "dev": true }, "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "dev": true, - "requires": { - "set-getter": "0.1.0" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true }, "lcid": { "version": "1.0.0", @@ -4029,6 +4601,14 @@ "pify": "2.3.0", "pinkie-promise": "2.0.1", "strip-bom": "2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "loader-runner": { @@ -4064,20 +4644,12 @@ "requires": { "p-locate": "2.0.0", "path-exists": "3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -4103,15 +4675,11 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, "lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", + "dev": true }, "lodash.memoize": { "version": "4.1.2", @@ -4120,9 +4688,9 @@ "dev": true }, "lodash.mergewith": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz", - "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", "dev": true }, "lodash.tail": { @@ -4157,9 +4725,9 @@ "dev": true }, "loglevel": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.0.tgz", - "integrity": "sha1-rgyqVhERSYxboTcj1vtjHSQAOTQ=", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", "dev": true }, "longest": { @@ -4185,9 +4753,9 @@ "dev": true }, "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", + "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", "dev": true, "requires": { "pseudomap": "1.0.2", @@ -4200,6 +4768,15 @@ "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", "dev": true }, + "make-dir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", + "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", + "dev": true, + "requires": { + "pify": "3.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -4228,15 +4805,34 @@ "dev": true }, "matrix-js-sdk": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.8.5.tgz", - "integrity": "sha1-1ZAVTx53ADVyZw+p28rH5APnbk8=", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.9.2.tgz", + "integrity": "sha1-KlIb3c9jfL796ROHhOYn6kNkW3c=", "requires": { "another-json": "0.2.0", + "babel-runtime": "6.26.0", "bluebird": "3.5.1", "browser-request": "0.3.3", "content-type": "1.0.4", - "request": "2.83.0" + "request": "2.85.0" + } + }, + "matrix-js-snippets": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/matrix-js-snippets/-/matrix-js-snippets-0.2.5.tgz", + "integrity": "sha512-Q/asN11Bd9giHnQ5kWRPixkAErUJKwprUSLPM5n2ju2W7JgqkSdXhSWBZ7YM9Yc9pc/W8Sb6t1CHAbdbyK3ArA==", + "requires": { + "chalk": "2.3.0", + "moment": "2.20.0", + "typescript": "2.7.2", + "winston": "2.4.0" + }, + "dependencies": { + "moment": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.0.tgz", + "integrity": "sha512-r7aEpLB/mhMUiC5ksahDajF/Jr3wS/qLzUnwOJCZyKWF34ibdvW8saujBKfR7aQlov//JgFA38HXOoIt7lXzcA==" + } } }, "md5.js": { @@ -4272,17 +4868,22 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.1.0" + "mimic-fn": "1.2.0" } }, + "memory-cache": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", + "integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo=" + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "0.1.4", - "readable-stream": "2.3.3" + "errno": "0.1.7", + "readable-stream": "2.3.5" } }, "meow": { @@ -4322,24 +4923,24 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.4.tgz", - "integrity": "sha512-kFRtviKYoAJT+t7HggMl0tBFGNAKLw/S7N+CO9qfEQyisob1Oy4pao+geRbkyeEd+V9aOkvZ4mhuyPvI/q9Sfg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.9.tgz", + "integrity": "sha512-SlIz6sv5UPaAVVFRKodKjCg48EbNoIhgetzfK/Cy0v5U52Z6zB136M8tp0UC9jM53LYbmIRihJszvvqpKkfm9g==", "dev": true, "requires": { "arr-diff": "4.0.0", "array-unique": "0.3.2", - "braces": "2.3.0", - "define-property": "1.0.0", - "extend-shallow": "2.0.1", - "extglob": "2.0.2", + "braces": "2.3.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", "fragment-cache": "0.2.1", "kind-of": "6.0.2", - "nanomatch": "1.2.6", + "nanomatch": "1.2.9", "object.pick": "1.3.0", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "miller-rabin": { @@ -4353,10 +4954,9 @@ } }, "mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", - "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", - "dev": true + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz", + "integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA==" }, "mime-db": { "version": "1.30.0", @@ -4372,11 +4972,16 @@ } }, "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, + "mini-deep-assign": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mini-deep-assign/-/mini-deep-assign-0.0.8.tgz", + "integrity": "sha1-FPpMrZPUfPu0OSeWqLABXn9gy80=" + }, "minimalistic-assert": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", @@ -4402,10 +5007,28 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "duplexify": "3.5.4", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.3", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "2.0.1", + "pumpify": "1.4.0", + "stream-each": "1.2.2", + "through2": "2.0.3" + } + }, "mixin-deep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.0.tgz", - "integrity": "sha512-dgaCvoh6i1nosAUBKb0l0pfJ78K8+S9fluyIR2YvAeUD/QuMahnFnF3xYty5eYXMjhGSsB0DsW6A0uAZyetoAg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { "for-in": "1.0.2", @@ -4449,37 +5072,73 @@ "minimist": "0.0.8" } }, + "module-parent": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/module-parent/-/module-parent-0.0.2.tgz", + "integrity": "sha1-vhy7PLalUGD8hkZ9Ly0s0KYA4Zk=" + }, "moment": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz", - "integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8=" + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, "moment-timezone": { "version": "0.5.14", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", "requires": { - "moment": "2.19.3" + "moment": "2.21.0" } }, - "mongodb-uri": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz", - "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "1.2.0", + "copy-concurrently": "1.0.5", + "fs-write-stream-atomic": "1.0.10", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" + } }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz", + "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=", + "requires": { + "append-field": "0.1.0", + "busboy": "0.2.14", + "concat-stream": "1.6.2", + "mkdirp": "0.5.1", + "object-assign": "3.0.0", + "on-finished": "2.3.0", + "type-is": "1.6.15", + "xtend": "4.0.1" + }, + "dependencies": { + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + } + } + }, "multicast-dns": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.1.tgz", - "integrity": "sha512-uV3/ckdsffHx9IrGQrx613mturMdMqQ06WTq+C09NsStJ9iNG6RcUWgPKs1Rfjy+idZT6tfQoXEusGNnEZhT3w==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "dev": true, "requires": { - "dns-packet": "1.2.2", - "thunky": "0.1.0" + "dns-packet": "1.3.1", + "thunky": "1.0.2" } }, "multicast-dns-service-types": { @@ -4488,42 +5147,30 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" - }, "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", "dev": true }, "nanomatch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.6.tgz", - "integrity": "sha512-WJ6XTCbvWXUFPbi/bDwKcYkCeOGUHzaJj72KbuPqGn78Ba/F5Vu26Zlo6SuMQbCIst1RGKL1zfWBCOGAlbRLAg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", "dev": true, "requires": { "arr-diff": "4.0.0", "array-unique": "0.3.2", - "define-property": "1.0.0", - "extend-shallow": "2.0.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", "fragment-cache": "0.2.1", - "is-odd": "1.0.0", - "kind-of": "5.1.0", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", "object.pick": "1.3.0", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.1" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "ncname": { @@ -4535,25 +5182,38 @@ "xml-char-classes": "1.0.0" } }, - "ncp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", - "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=" - }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "neo-async": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.0.tgz", + "integrity": "sha512-nJmSswG4As/MkRq7QZFuH/sf/yuv8ODdMZrY4Bedjp77a5MK4A6s7YbBB64c9u79EBUOfXUXBvArmvzTD0X+6g==", + "dev": true + }, "netmask": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=" }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "ng2-breadcrumbs": { + "version": "0.1.281", + "resolved": "https://registry.npmjs.org/ng2-breadcrumbs/-/ng2-breadcrumbs-0.1.281.tgz", + "integrity": "sha1-OKZsYoD7BgwacyQ7ezP3zZ88waY=", + "dev": true + }, "ngx-modialog": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/ngx-modialog/-/ngx-modialog-3.0.4.tgz", - "integrity": "sha512-rkjLI2GPsikdEKqDxooBdTl8BXh4ndGnLg8iLk4xGNSq+ltRzm7QH4C62V8XcKdG7Kk7VSVc7tGKMtooxLcORQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ngx-modialog/-/ngx-modialog-5.0.0.tgz", + "integrity": "sha512-EYqlwoHHMzKBhQegIVIAKGqAk23riah485tEFFdiPHeNbtgBfpSUJ8L7fW3YtHQGuV2tUc+mU8Ty728VwPP1mQ==", "dev": true }, "no-case": { @@ -4569,22 +5229,18 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, "requires": { "encoding": "0.1.12", "is-stream": "1.1.0" } }, "node-forge": { - "version": "0.6.33", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.33.tgz", - "integrity": "sha1-RjgRh59XPUUVWtap9D3ClujoXrw=", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", + "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=", "dev": true }, - "node-fs": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/node-fs/-/node-fs-0.1.7.tgz", - "integrity": "sha1-MjI8zLRsn78PwRgS1FAhzDHTJbs=" - }, "node-gyp": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", @@ -4598,8 +5254,8 @@ "mkdirp": "0.5.1", "nopt": "3.0.6", "npmlog": "4.1.2", - "osenv": "0.1.4", - "request": "2.83.0", + "osenv": "0.1.5", + "request": "2.85.0", "rimraf": "2.6.2", "semver": "5.3.0", "tar": "2.2.1", @@ -4626,7 +5282,7 @@ "console-browserify": "1.1.0", "constants-browserify": "1.0.0", "crypto-browserify": "3.12.0", - "domain-browser": "1.1.7", + "domain-browser": "1.2.0", "events": "1.1.1", "https-browserify": "1.0.0", "os-browserify": "0.3.0", @@ -4634,11 +5290,11 @@ "process": "0.11.10", "punycode": "1.4.1", "querystring-es3": "0.2.1", - "readable-stream": "2.3.3", + "readable-stream": "2.3.5", "stream-browserify": "2.0.1", - "stream-http": "2.7.2", + "stream-http": "2.8.1", "string_decoder": "1.0.3", - "timers-browserify": "2.0.4", + "timers-browserify": "2.0.6", "tty-browserify": "0.0.0", "url": "0.11.0", "util": "0.10.3", @@ -4646,9 +5302,9 @@ } }, "node-sass": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.7.2.tgz", - "integrity": "sha512-CaV+wLqZ7//Jdom5aUFCpGNoECd7BbNhjuwdsX/LkXBrHl8eb1Wjw4HvWqcFvhr5KuNgAk8i/myf/MQ1YYeroA==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.8.3.tgz", + "integrity": "sha512-tfFWhUsCk/Y19zarDcPo5xpj+IW3qCfOjVdHtYeG6S1CKbQOh1zqylnQK6cV3z9k80yxAnFX9Y+a9+XysDhhfg==", "dev": true, "requires": { "async-foreach": "0.1.3", @@ -4660,10 +5316,10 @@ "in-publish": "2.0.0", "lodash.assign": "4.2.0", "lodash.clonedeep": "4.5.0", - "lodash.mergewith": "4.6.0", + "lodash.mergewith": "4.6.1", "meow": "3.7.0", "mkdirp": "0.5.1", - "nan": "2.8.0", + "nan": "2.10.0", "node-gyp": "3.6.2", "npmlog": "4.1.2", "request": "2.79.0", @@ -4734,7 +5390,7 @@ "dev": true, "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "mime-types": "2.1.17" } }, @@ -4746,7 +5402,7 @@ "requires": { "chalk": "1.1.3", "commander": "2.11.0", - "is-my-json-valid": "2.16.1", + "is-my-json-valid": "2.17.2", "pinkie-promise": "2.0.1" } }, @@ -4776,7 +5432,7 @@ "requires": { "assert-plus": "0.2.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" } }, "qs": { @@ -4794,7 +5450,7 @@ "aws-sign2": "0.6.0", "aws4": "1.6.0", "caseless": "0.11.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "2.1.4", @@ -4808,9 +5464,9 @@ "oauth-sign": "0.8.2", "qs": "6.3.2", "stringstream": "0.0.5", - "tough-cookie": "2.3.3", + "tough-cookie": "2.3.4", "tunnel-agent": "0.4.3", - "uuid": "3.1.0" + "uuid": "3.2.1" } }, "sntp": { @@ -4866,10 +5522,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.5.0", + "hosted-git-info": "2.6.0", "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" } }, "normalize-path": { @@ -4949,8 +5605,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -4972,6 +5627,24 @@ "is-descriptor": "0.1.6" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -4997,7 +5670,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -5048,9 +5721,9 @@ } }, "obuf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", - "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, "on-finished": { @@ -5076,29 +5749,20 @@ } }, "onecolor": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.0.4.tgz", - "integrity": "sha1-daRvgNpseqpbTarhekcZi9llJJQ=", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.0.5.tgz", + "integrity": "sha1-Nu/zIgE3nv3xGA+0ReUajiQl+fY=", "dev": true }, "opn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz", - "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", "dev": true, "requires": { "is-wsl": "1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" - } - }, "original": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", @@ -5147,9 +5811,9 @@ "dev": true }, "osenv": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", - "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "requires": { "os-homedir": "1.0.2", @@ -5163,10 +5827,13 @@ "dev": true }, "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } }, "p-locate": { "version": "2.0.0", @@ -5174,7 +5841,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.1.0" + "p-limit": "1.2.0" } }, "p-map": { @@ -5183,12 +5850,29 @@ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", "dev": true }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "pako": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", "dev": true }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, "param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -5198,27 +5882,27 @@ "no-case": "2.3.1" } }, + "parent-module": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-0.1.0.tgz", + "integrity": "sha1-tSkoY6HoxHbs+Ffn11yYkgskuKY=", + "requires": { + "callsites": "1.0.1" + } + }, "parse-asn1": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", "dev": true, "requires": { - "asn1.js": "4.9.2", + "asn1.js": "4.10.1", "browserify-aes": "1.1.1", "create-hash": "1.1.3", "evp_bytestokey": "1.0.3", "pbkdf2": "3.0.14" } }, - "parse-database-url": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/parse-database-url/-/parse-database-url-0.3.0.tgz", - "integrity": "sha1-NpZmMh6SfJreY838Gqr2+zdFPQ0=", - "requires": { - "mongodb-uri": "0.9.7" - } - }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -5268,20 +5952,31 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "requires": { + "process": "0.11.10", + "util": "0.10.3" + } + }, "path-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "2.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -5311,14 +6006,12 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "pify": "3.0.0" } }, "pbkdf2": { @@ -5331,7 +6024,7 @@ "create-hmac": "1.1.6", "ripemd160": "2.0.1", "safe-buffer": "5.1.1", - "sha.js": "2.4.9" + "sha.js": "2.4.11" } }, "performance-now": { @@ -5340,22 +6033,20 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "2.0.4" } @@ -5366,30 +6057,77 @@ "integrity": "sha1-LaSh3m7EQjxfw3lOkwuB1EkOxoY=", "dev": true, "requires": { - "browserslist": "2.3.3", - "postcss": "6.0.9", + "browserslist": "2.11.3", + "postcss": "6.0.20", "reduce-css-calc": "1.3.0" }, "dependencies": { - "browserslist": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.3.3.tgz", - "integrity": "sha512-p9hz6FA2H1w1ZUAXKfK3MlIA4Z9fEd56hnZSOecBIITb5j0oZk/tZRwhdE0xG56RGx2x8cc1c5AWJKWVjMLOEQ==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000718", - "electron-to-chromium": "1.3.18" + "color-convert": "1.9.0" } }, - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "caniuse-lite": "1.0.30000817", + "electron-to-chromium": "1.3.40" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "electron-to-chromium": { + "version": "1.3.40", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.40.tgz", + "integrity": "sha1-H71tl779crim+SHcONIkE9L2/d8=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5401,43 +6139,68 @@ "dev": true, "requires": { "find-up": "2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - } } }, - "pkginfo": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", - "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=" - }, "pleeease-filters": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pleeease-filters/-/pleeease-filters-4.0.0.tgz", "integrity": "sha1-ZjKy+wVkjSdY2GU4T7zteeHMrsc=", "dev": true, "requires": { - "onecolor": "3.0.4", - "postcss": "6.0.9" + "onecolor": "3.0.5", + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5531,18 +6294,59 @@ "requires": { "babel-runtime": "6.26.0", "balanced-match": "0.4.2", - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5553,19 +6357,60 @@ "integrity": "sha1-lNxCLI+QmX8WvTOjZUu77AhJY7Q=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-selector-parser": "2.2.3" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5582,52 +6427,122 @@ } }, "postcss-color-function": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-function/-/postcss-color-function-4.0.0.tgz", - "integrity": "sha1-fgEG9Pah7LGtWzqFU6zl6Ciq4Yc=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-function/-/postcss-color-function-4.0.1.tgz", + "integrity": "sha1-QCs/LOvD9pR+YY+2vjZU++zvZEQ=", "dev": true, "requires": { - "css-color-function": "1.3.0", - "postcss": "6.0.9", + "css-color-function": "1.3.3", + "postcss": "6.0.20", "postcss-message-helpers": "2.0.0", "postcss-value-parser": "3.3.0" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } }, "postcss-color-gray": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-4.0.0.tgz", - "integrity": "sha1-aBvzBQl91mv+8OHmKC1dmbWsyV0=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz", + "integrity": "sha512-L4iLKQLdqChz6ZOgGb6dRxkBNw78JFYcJmBz1orHpZoeLtuhDDGegRtX9gSyfoCIM7rWZ3VNOyiqqvk83BEN+w==", "dev": true, "requires": { - "color": "1.0.3", - "postcss": "6.0.9", + "color": "2.0.1", + "postcss": "6.0.20", "postcss-message-helpers": "2.0.0", "reduce-function-call": "1.0.2" }, "dependencies": { - "color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", - "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0", + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "color": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color/-/color-2.0.1.tgz", + "integrity": "sha512-ubUCVVKfT7r2w2D3qtHakj8mbmKms+tThR8gI8zEYCbUBl8/voqFGt3kgBqGwXAopgXybnkuOq+qMYCRrp4cXw==", + "dev": true, + "requires": { + "color-convert": "1.9.1", "color-string": "1.5.2" } }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, "color-string": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.2.tgz", @@ -5638,15 +6553,36 @@ "simple-swizzle": "0.2.2" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5658,10 +6594,30 @@ "dev": true, "requires": { "color": "1.0.3", - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-message-helpers": "2.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, "color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", @@ -5682,15 +6638,36 @@ "simple-swizzle": "0.2.2" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5701,20 +6678,61 @@ "integrity": "sha1-EnA2ZvoxBDDj8wpFTawThjF9WEQ=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-value-parser": "3.3.0", "units-css": "0.4.0" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5726,11 +6744,31 @@ "dev": true, "requires": { "color": "1.0.3", - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-message-helpers": "2.0.0", "reduce-function-call": "1.0.2" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, "color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", @@ -5751,15 +6789,36 @@ "simple-swizzle": "0.2.2" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5770,19 +6829,60 @@ "integrity": "sha1-7rrwPTY7QwC5Z5K9MIHBntZlE9M=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-value-parser": "3.3.0" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5793,19 +6893,60 @@ "integrity": "sha1-FFOcinExSUtILg3RzCZf9lFLUmM=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-value-parser": "3.3.0" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5816,20 +6957,61 @@ "integrity": "sha1-N9XJNToHoJJwkSqCYGu0Kg1wLAQ=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-value-parser": "3.3.0", "rgb-hex": "2.1.0" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -5856,22 +7038,22 @@ } }, "postcss-cssnext": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-cssnext/-/postcss-cssnext-3.0.2.tgz", - "integrity": "sha512-jA6kGdcUMZqLUgw6MdpyNWGFhk0LIITVhC/jTnLRZLoXSTR88qT2cFOn3LbY06udt1PVdTCHDG3plBjxVKf8BQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz", + "integrity": "sha512-awPDhI4OKetcHCr560iVCoDuP6e/vn0r6EAqdWPpAavJMvkBSZ6kDpSN4b3mB3Ti57hQMunHHM8Wvx9PeuYXtA==", "dev": true, "requires": { - "autoprefixer": "7.1.2", + "autoprefixer": "7.2.6", "caniuse-api": "2.0.0", "chalk": "2.3.0", "pixrem": "4.0.1", "pleeease-filters": "4.0.0", - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-apply": "0.8.0", "postcss-attribute-case-insensitive": "2.0.0", - "postcss-calc": "6.0.0", - "postcss-color-function": "4.0.0", - "postcss-color-gray": "4.0.0", + "postcss-calc": "6.0.1", + "postcss-color-function": "4.0.1", + "postcss-color-gray": "4.1.0", "postcss-color-hex-alpha": "3.0.0", "postcss-color-hsl": "2.0.0", "postcss-color-hwb": "3.0.0", @@ -5879,14 +7061,14 @@ "postcss-color-rgb": "2.0.0", "postcss-color-rgba-fallback": "3.0.0", "postcss-custom-media": "6.0.0", - "postcss-custom-properties": "6.1.0", + "postcss-custom-properties": "6.3.1", "postcss-custom-selectors": "4.0.1", - "postcss-font-family-system-ui": "2.0.1", + "postcss-font-family-system-ui": "3.0.0", "postcss-font-variant": "3.0.0", "postcss-image-set-polyfill": "0.3.5", "postcss-initial": "2.0.0", "postcss-media-minmax": "3.0.0", - "postcss-nesting": "4.1.0", + "postcss-nesting": "4.2.1", "postcss-pseudo-class-any-link": "4.0.0", "postcss-pseudoelements": "5.0.0", "postcss-replace-overflow-wrap": "2.0.0", @@ -5894,28 +7076,37 @@ "postcss-selector-not": "3.0.1" }, "dependencies": { - "autoprefixer": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.2.tgz", - "integrity": "sha1-++rwfUj9h44Ggr98vurecorbKxg=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "browserslist": "2.3.3", - "caniuse-lite": "1.0.30000718", + "color-convert": "1.9.0" + } + }, + "autoprefixer": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", + "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", + "dev": true, + "requires": { + "browserslist": "2.11.3", + "caniuse-lite": "1.0.30000817", "normalize-range": "0.1.2", "num2fraction": "1.2.2", - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-value-parser": "3.3.0" } }, "browserslist": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.3.3.tgz", - "integrity": "sha512-p9hz6FA2H1w1ZUAXKfK3MlIA4Z9fEd56hnZSOecBIITb5j0oZk/tZRwhdE0xG56RGx2x8cc1c5AWJKWVjMLOEQ==", + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000718", - "electron-to-chromium": "1.3.18" + "caniuse-lite": "1.0.30000817", + "electron-to-chromium": "1.3.40" } }, "caniuse-api": { @@ -5924,44 +7115,84 @@ "integrity": "sha1-sd21pZZrFvSNxJmERNS7xsfZ2DQ=", "dev": true, "requires": { - "browserslist": "2.3.3", - "caniuse-lite": "1.0.30000718", + "browserslist": "2.11.3", + "caniuse-lite": "1.0.30000817", "lodash.memoize": "4.1.2", "lodash.uniq": "4.5.0" } }, + "electron-to-chromium": { + "version": "1.3.40", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.40.tgz", + "integrity": "sha1-H71tl779crim+SHcONIkE9L2/d8=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + }, + "dependencies": { + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + } } }, "postcss-calc": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-6.0.0.tgz", - "integrity": "sha1-toGyecbST74OM+2QRYA3BURdYTs=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-6.0.1.tgz", + "integrity": "sha1-PSQXG79udinUIqQ26/5t2VEfQzA=", "dev": true, "requires": { "css-unit-converter": "1.1.1", - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-selector-parser": "2.2.3", - "reduce-css-calc": "2.0.5" + "reduce-css-calc": "2.1.4" } }, "reduce-css-calc": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.0.5.tgz", - "integrity": "sha1-M8l4OMXUxxGlwU74XOT95BSD970=", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.4.tgz", + "integrity": "sha512-i/vWQbyd3aJRmip9OVSN9V6nIjLf/gg/ctxb0CpvHWtcRysFl/ngDBQD+rqavxdw/doScA3GMBXhzkHQ4GCzFQ==", "dev": true, "requires": { "css-unit-converter": "1.1.1", "postcss-value-parser": "3.3.0" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } } } }, @@ -5971,47 +7202,129 @@ "integrity": "sha1-vlMnhBEOyylQRPtTlaGABushpzc=", "dev": true, "requires": { - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } }, "postcss-custom-properties": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-6.1.0.tgz", - "integrity": "sha1-nK8RUaxBsenmTTov+ezplsoYl30=", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-6.3.1.tgz", + "integrity": "sha512-zoiwn4sCiUFbr4KcgcNZLFkR6gVQom647L+z1p/KBVHZ1OYwT87apnS42atJtx6XlX2yI7N5fjXbFixShQO2QQ==", "dev": true, "requires": { "balanced-match": "1.0.0", - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6022,19 +7335,60 @@ "integrity": "sha1-eBOC+UxS5yfvXKR3bqKt9JphE4I=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-selector-matches": "3.0.1" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6096,25 +7450,64 @@ } }, "postcss-font-family-system-ui": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-font-family-system-ui/-/postcss-font-family-system-ui-2.0.1.tgz", - "integrity": "sha1-MYoHX9y4S4ZKqCOlGTXvClhy6RE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz", + "integrity": "sha512-58G/hTxMSSKlIRpcPUjlyo6hV2MEzvcVO2m4L/T7Bb2fJTG4DYYfQjQeRvuimKQh1V1sOzCIz99g+H2aFNtlQw==", "dev": true, "requires": { - "lodash": "4.17.4", - "postcss": "6.0.9", - "postcss-value-parser": "3.3.0" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6125,18 +7518,59 @@ "integrity": "sha1-CMzIj2BQuoLtjvLMdsDGprQfGD4=", "dev": true, "requires": { - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6147,45 +7581,126 @@ "integrity": "sha1-Dxk0E3AM8fgr05Bm7wFtZaShgYE=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-media-query-parser": "0.2.3" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } }, "postcss-import": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-10.0.0.tgz", - "integrity": "sha1-TIXJewmRNsxeoCQNwd/b/eTi674=", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-11.1.0.tgz", + "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==", "dev": true, "requires": { - "object-assign": "4.1.1", - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-value-parser": "3.3.0", "read-cache": "1.0.0", "resolve": "1.4.0" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6197,18 +7712,59 @@ "dev": true, "requires": { "lodash.template": "4.4.0", - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6246,26 +7802,62 @@ } }, "postcss-loader": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.0.9.tgz", - "integrity": "sha512-sgoXPtmgVT3aBAhU47Kig8oPF+mbXl8Unjvtz1Qj1q2D2EvSVJW2mKJNzxv5y/LvA9xWwuvdysvhc7Zn80UWWw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.3.tgz", + "integrity": "sha512-RuBcNE8rjCkIB0IsbmkGFRmQJTeQJfCI88E0VTarPNTvaNSv9OFv1DvTwgtAN/qlzyiELsmmmtX/tEzKp/cdug==", "dev": true, "requires": { "loader-utils": "1.1.0", - "postcss": "6.0.14", + "postcss": "6.0.20", "postcss-load-config": "1.2.0", - "schema-utils": "0.3.0" + "schema-utils": "0.4.5" }, "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", "source-map": "0.6.1", - "supports-color": "4.5.0" + "supports-color": "5.3.0" + } + }, + "schema-utils": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", + "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", + "dev": true, + "requires": { + "ajv": "6.3.0", + "ajv-keywords": "3.1.0" } }, "source-map": { @@ -6275,12 +7867,12 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -6291,18 +7883,59 @@ "integrity": "sha1-Z1JWA3pD70C8Twdgv9BtTcadSNI=", "dev": true, "requires": { - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6398,23 +8031,49 @@ } }, "postcss-modules-extract-imports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", - "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", + "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", "dev": true, "requires": { - "postcss": "6.0.14" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", "source-map": "0.6.1", - "supports-color": "4.5.0" + "supports-color": "5.3.0" } }, "source-map": { @@ -6424,12 +8083,12 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -6441,18 +8100,44 @@ "dev": true, "requires": { "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.14" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", "source-map": "0.6.1", - "supports-color": "4.5.0" + "supports-color": "5.3.0" } }, "source-map": { @@ -6462,12 +8147,12 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -6479,18 +8164,44 @@ "dev": true, "requires": { "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.14" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", "source-map": "0.6.1", - "supports-color": "4.5.0" + "supports-color": "5.3.0" } }, "source-map": { @@ -6500,12 +8211,12 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -6517,18 +8228,44 @@ "dev": true, "requires": { "icss-replace-symbols": "1.1.0", - "postcss": "6.0.14" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", "source-map": "0.6.1", - "supports-color": "4.5.0" + "supports-color": "5.3.0" } }, "source-map": { @@ -6538,34 +8275,75 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } }, "postcss-nesting": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-4.1.0.tgz", - "integrity": "sha512-jn5bGJMSAawifyj2cXcqDw7mWt36HKY3TBCPlzkYpgOAwZcHpM5C5K5xPdj97pmdj5Q5SCpaFobt9ONitYo5lg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-4.2.1.tgz", + "integrity": "sha512-IkyWXICwagCnlaviRexi7qOdwPw3+xVVjgFfGsxmztvRVaNxAlrypOIKqDE5mxY+BVxnId1rnUKBRQoNE2VDaA==", "dev": true, "requires": { - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6607,19 +8385,60 @@ "integrity": "sha1-kVKgYT00UHIFE+iJKFS65C0O5o4=", "dev": true, "requires": { - "postcss": "6.0.9", + "postcss": "6.0.20", "postcss-selector-parser": "2.2.3" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6630,18 +8449,59 @@ "integrity": "sha1-7vGU6NUkZFylIKlJ6V5RjoEkAss=", "dev": true, "requires": { - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6682,40 +8542,122 @@ "integrity": "sha1-eU22+qVPjbEAhUOSqTr0V2i04ls=", "dev": true, "requires": { - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } }, "postcss-scss": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.2.tgz", - "integrity": "sha1-/0XPM1S4ee6JpOtoaA9GrJuxT5Q=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.4.tgz", + "integrity": "sha512-IFj42Hz2cBHHFvZTqkJqU08JCCM/MZU5/uNkTUZBaBFP2d4C5unw4HyCL52RfCwJb6KoVUD3eoepxMh1dfBFCQ==", "dev": true, "requires": { - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6727,18 +8669,59 @@ "dev": true, "requires": { "balanced-match": "0.4.2", - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6750,18 +8733,59 @@ "dev": true, "requires": { "balanced-match": "0.4.2", - "postcss": "6.0.9" + "postcss": "6.0.20" }, "dependencies": { - "postcss": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.9.tgz", - "integrity": "sha512-bBE2AHNEBhF23TfET6AA/lFP8ah+qHOZoFJEflFG+HgvVLdTmMOrocx/4LVVDIn3w6jUssw1q2Exk1cc9UOI8w==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "chalk": "2.3.0", - "source-map": "0.5.7", - "supports-color": "4.2.1" + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.20.tgz", + "integrity": "sha512-Opr6usW30Iy0xEDrJywDckRxtylfO7gTGs3Kfb2LdLQlGsUg89fTy0R3Vm1Dub2YHO7MK58avr0p70+uFFHb7A==", + "dev": true, + "requires": { + "chalk": "2.3.2", + "source-map": "0.6.1", + "supports-color": "5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } @@ -6840,87 +8864,46 @@ } }, "private": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", - "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, "promise-polyfill": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.0.2.tgz", - "integrity": "sha1-2chtPcTcLfkBboiUbe/Wm0m0EWI=" - }, - "prompt": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", - "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", - "requires": { - "colors": "1.1.2", - "pkginfo": "0.4.1", - "read": "1.0.7", - "revalidator": "0.1.8", - "utile": "0.3.0", - "winston": "2.1.1" - }, - "dependencies": { - "async": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", - "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" - }, - "winston": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", - "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", - "requires": { - "async": "1.0.0", - "colors": "1.0.3", - "cycle": "1.0.3", - "eyes": "0.1.8", - "isstream": "0.1.2", - "pkginfo": "0.3.1", - "stack-trace": "0.0.10" - }, - "dependencies": { - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" - }, - "pkginfo": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", - "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=" - } - } - } - } + "integrity": "sha1-2chtPcTcLfkBboiUbe/Wm0m0EWI=", + "dev": true }, "proxy-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", + "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", "requires": { "forwarded": "0.1.2", - "ipaddr.js": "1.5.2" + "ipaddr.js": "1.6.0" } }, "prr": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", - "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, "pseudomap": { @@ -6939,7 +8922,28 @@ "browserify-rsa": "4.0.1", "create-hash": "1.1.3", "parse-asn1": "5.1.0", - "randombytes": "2.0.5" + "randombytes": "2.0.6" + } + }, + "pump": { + "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.4.1", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.4.0.tgz", + "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", + "dev": true, + "requires": { + "duplexify": "3.5.4", + "inherits": "2.0.3", + "pump": "2.0.1" } }, "punycode": { @@ -7012,27 +9016,27 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } }, "randombytes": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", - "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { "safe-buffer": "5.1.1" } }, "randomfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.3.tgz", - "integrity": "sha512-YL6GrhrWoic0Eq8rXVbMptH7dAxCs0J+mh5Y0euNekPPYaxEmdVGim6GdoxoRzKW2yJoU8tueifS7mYxvcFDEQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "2.0.5", + "randombytes": "2.0.6", "safe-buffer": "5.1.1" } }, @@ -7065,32 +9069,6 @@ "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", "dev": true }, - "rc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", - "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "requires": { - "mute-stream": "0.0.7" - } - }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -7098,6 +9076,14 @@ "dev": true, "requires": { "pify": "2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "read-pkg": { @@ -7109,6 +9095,25 @@ "load-json-file": "1.1.0", "normalize-package-data": "2.4.0", "path-type": "1.1.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "read-pkg-up": { @@ -7119,18 +9124,38 @@ "requires": { "find-up": "1.1.2", "read-pkg": "1.1.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + } } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -7144,7 +9169,7 @@ "requires": { "graceful-fs": "4.1.11", "minimatch": "3.0.4", - "readable-stream": "2.3.3", + "readable-stream": "2.3.5", "set-immediate-shim": "1.0.1" } }, @@ -7156,23 +9181,14 @@ "requires": { "ast-types": "0.9.6", "esprima": "3.1.3", - "private": "0.1.7", + "private": "0.1.8", "source-map": "0.5.7" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - } } }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, "requires": { "resolve": "1.4.0" } @@ -7208,10 +9224,9 @@ } }, "reflect-metadata": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz", - "integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo=", - "dev": true + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" }, "regenerate": { "version": "1.3.3", @@ -7222,8 +9237,7 @@ "regenerator-runtime": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", - "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", - "dev": true + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==" }, "regex-cache": { "version": "0.4.4", @@ -7235,12 +9249,13 @@ } }, "regex-not": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.0.tgz", - "integrity": "sha1-Qvg+OXcWIt+CawKvF2Ul1qXxV/k=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "2.0.1" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" } }, "regexpu-core": { @@ -7324,17 +9339,17 @@ } }, "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "requires": { "aws-sign2": "0.7.0", "aws4": "1.6.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.1", + "form-data": "2.3.2", "har-validator": "5.0.3", "hawk": "6.0.2", "http-signature": "1.2.0", @@ -7347,9 +9362,18 @@ "qs": "6.5.1", "safe-buffer": "5.1.1", "stringstream": "0.0.5", - "tough-cookie": "2.3.3", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.2.1" + } + }, + "require-dir-all": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/require-dir-all/-/require-dir-all-0.4.15.tgz", + "integrity": "sha1-TpkogxrfHhixXTlDyX+RrIhVezs=", + "requires": { + "mini-deep-assign": "0.0.8", + "module-parent": "0.0.2" } }, "require-directory": { @@ -7364,6 +9388,35 @@ "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", "dev": true }, + "require-glob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/require-glob/-/require-glob-3.2.0.tgz", + "integrity": "sha1-kL/iyO+0ufly65o/XlgIMuBPZNM=", + "requires": { + "glob-parent": "3.1.0", + "globby": "6.1.0", + "parent-module": "0.1.0" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -7405,6 +9458,12 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, "retry-as-promised": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", @@ -7414,11 +9473,6 @@ "debug": "2.6.9" } }, - "revalidator": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", - "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=" - }, "rgb": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/rgb/-/rgb-0.1.0.tgz", @@ -7444,6 +9498,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, "requires": { "glob": "7.1.2" } @@ -7458,10 +9513,19 @@ "inherits": "2.0.3" } }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "1.2.0" + } + }, "rxjs": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.5.tgz", - "integrity": "sha512-D/MfQnPMBk8P8gfwGxvCkuaWBcG58W7dUMT//URPoYzIbDEKT0GezdirkK5whMgKFBATfCoTpxO8bJQGJ04W5A==", + "version": "5.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.7.tgz", + "integrity": "sha512-Hxo2ac8gRQjwjtKgukMIwBRbq5+KAeEV5hXM4obYBOAghev41bDQWgFH4svYiU9UnQ5kNww2LgfyBdevCd2HXA==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -7472,6 +9536,15 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, "sass-graph": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", @@ -7479,39 +9552,22 @@ "dev": true, "requires": { "glob": "7.1.2", - "lodash": "4.17.4", + "lodash": "4.17.5", "scss-tokenizer": "0.2.3", "yargs": "7.1.0" } }, "sass-loader": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.6.tgz", - "integrity": "sha512-c3/Zc+iW+qqDip6kXPYLEgsAu2lf4xz0EZDplB7EmSUMda12U1sGJPetH55B/j9eu0bTtKzKlNPWWyYC7wFNyQ==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.7.tgz", + "integrity": "sha512-JoiyD00Yo1o61OJsoP2s2kb19L1/Y2p3QFcCdWdF6oomBGKVYuZyqHWemRBfQ2uGYsk+CH3eCguXNfpjzlcpaA==", "dev": true, "requires": { - "async": "2.5.0", - "clone-deep": "0.3.0", + "clone-deep": "2.0.2", "loader-utils": "1.1.0", "lodash.tail": "4.1.1", + "neo-async": "2.5.0", "pify": "3.0.0" - }, - "dependencies": { - "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } } }, "sax": { @@ -7546,7 +9602,8 @@ "screenfull": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-3.3.2.tgz", - "integrity": "sha512-zrnT8EidEWGFkmXEa1d/YUYNvvJaMX05g4O82K+Oiy9jR6Fh3ZTsovsccJOjRJyhS0KPV7AQpDFQRAYXBl9a5A==" + "integrity": "sha512-zrnT8EidEWGFkmXEa1d/YUYNvvJaMX05g4O82K+Oiy9jR6Fh3ZTsovsccJOjRJyhS0KPV7AQpDFQRAYXBl9a5A==", + "dev": true }, "scss-tokenizer": { "version": "0.2.3", @@ -7576,18 +9633,18 @@ "dev": true }, "selfsigned": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.1.tgz", - "integrity": "sha1-v4y3uDJWxFUeMTR8YxF3jbme7FI=", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.2.tgz", + "integrity": "sha1-tESVgNmZKbZbEKSDiTAaZZIIh1g=", "dev": true, "requires": { - "node-forge": "0.6.33" + "node-forge": "0.7.1" } }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "semver-dsl": { "version": "1.0.1", @@ -7595,18 +9652,18 @@ "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", "dev": true, "requires": { - "semver": "5.4.1" + "semver": "5.5.0" } }, "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", "requires": { "debug": "2.6.9", - "depd": "1.1.1", + "depd": "1.1.2", "destroy": "1.0.4", - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "etag": "1.8.1", "fresh": "0.5.2", @@ -7615,43 +9672,43 @@ "ms": "2.0.0", "on-finished": "2.3.0", "range-parser": "1.2.0", - "statuses": "1.3.1" + "statuses": "1.4.0" }, "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" } } }, "sequelize": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.27.0.tgz", - "integrity": "sha512-HO4dz8sexVIgQtm5K+YqKLah1dWY9BK0tCRwUVbjB/VQCyqmJ73Dm+5Ci0C1oXSjMvWKsPK/GUgJuQsAPE0KsA==", + "version": "4.37.3", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.37.3.tgz", + "integrity": "sha512-4hTKikL3T8YovUYKJiqFw6I96WuDtsSEhuClL+CZk6gVq97F45tgAlYt+uYJGipGyr4HW4HV5pJ6STJ1e21W+g==", "requires": { "bluebird": "3.5.1", "cls-bluebird": "2.1.0", "debug": "3.1.0", "depd": "1.1.1", "dottie": "2.0.0", - "generic-pool": "3.2.0", + "generic-pool": "3.4.2", "inflection": "1.12.0", - "lodash": "4.17.4", - "moment": "2.19.3", + "lodash": "4.17.5", + "moment": "2.21.0", "moment-timezone": "0.5.14", "retry-as-promised": "2.3.2", - "semver": "5.4.1", + "semver": "5.5.0", "terraformer-wkt-parser": "1.1.2", "toposort-class": "1.0.1", - "uuid": "3.1.0", - "validator": "9.2.0", - "wkx": "0.4.2" + "uuid": "3.2.1", + "validator": "9.4.1", + "wkx": "0.4.4" }, "dependencies": { "debug": { @@ -7664,13 +9721,39 @@ } } }, + "sequelize-typescript": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/sequelize-typescript/-/sequelize-typescript-0.6.3.tgz", + "integrity": "sha512-Mj+/g8QPqWJ+0h7IsmHUzJziu8vINKPZFhBBkClF066gPOHOz3xay+iQudVkdVdMEjMRqwfp2YoPARSqr+r5Fw==", + "requires": { + "@types/bluebird": "3.5.18", + "@types/node": "6.0.41", + "@types/reflect-metadata": "0.0.4", + "@types/sequelize": "4.27.5", + "es6-shim": "0.35.3", + "glob": "7.1.2" + }, + "dependencies": { + "@types/node": { + "version": "6.0.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.41.tgz", + "integrity": "sha1-V4z1Oq7GWIe8rxZ5L4ciky6P+Oo=" + } + } + }, + "serialize-javascript": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.4.0.tgz", + "integrity": "sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=", + "dev": true + }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "accepts": "1.3.4", + "accepts": "1.3.5", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "1.0.3", @@ -7680,14 +9763,14 @@ } }, "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "requires": { - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "parseurl": "1.3.2", - "send": "0.16.1" + "send": "0.16.2" } }, "set-blocking": { @@ -7696,15 +9779,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", - "dev": true, - "requires": { - "to-object-path": "0.3.0" - } - }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", @@ -7721,6 +9795,17 @@ "is-extendable": "0.1.1", "is-plain-object": "2.0.4", "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } } }, "setimmediate": { @@ -7735,9 +9820,9 @@ "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" }, "sha.js": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", - "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { "inherits": "2.0.3", @@ -7745,30 +9830,20 @@ } }, "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", "dev": true, "requires": { "is-extendable": "0.1.1", - "kind-of": "2.0.1", - "lazy-cache": "0.2.7", + "kind-of": "5.1.0", "mixin-object": "2.0.1" }, "dependencies": { "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } @@ -7827,10 +9902,16 @@ } } }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, "snapdragon": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", - "integrity": "sha1-4StUh/re0+PeoKyR6UAL91tAE3A=", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { "base": "0.11.2", @@ -7840,7 +9921,7 @@ "map-cache": "0.2.2", "source-map": "0.5.7", "source-map-resolve": "0.5.1", - "use": "2.0.2" + "use": "3.1.0" }, "dependencies": { "define-property": { @@ -7852,6 +9933,55 @@ "is-descriptor": "0.1.6" } }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -7880,6 +10010,17 @@ "define-property": "1.0.0", "isobject": "3.0.1", "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + } } }, "snapdragon-util": { @@ -7897,7 +10038,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } @@ -7907,25 +10048,17 @@ "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, "sockjs": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.18.tgz", - "integrity": "sha1-2bKJMWyn33dZXvKZ4HXw+TfrQgc=", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "dev": true, "requires": { "faye-websocket": "0.10.0", - "uuid": "2.0.3" - }, - "dependencies": { - "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "dev": true - } + "uuid": "3.2.1" } }, "sockjs-client": { @@ -7988,12 +10121,20 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "source-map-url": { @@ -8003,24 +10144,35 @@ "dev": true }, "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "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==", + "dev": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, "spdy": { @@ -8034,51 +10186,37 @@ "http-deceiver": "1.2.7", "safe-buffer": "5.1.1", "select-hose": "2.0.0", - "spdy-transport": "2.0.20" + "spdy-transport": "2.1.0" } }, "spdy-transport": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.0.20.tgz", - "integrity": "sha1-c15yBUxIayNU/onnAiVgBKOazk0=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", + "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", "dev": true, "requires": { "debug": "2.6.9", "detect-node": "2.0.3", "hpack.js": "2.1.6", - "obuf": "1.1.1", - "readable-stream": "2.3.3", + "obuf": "1.1.2", + "readable-stream": "2.3.5", "safe-buffer": "5.1.1", - "wbuf": "1.7.2" + "wbuf": "1.7.3" } }, + "spinkit": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/spinkit/-/spinkit-1.2.5.tgz", + "integrity": "sha1-kPn0ZqIOjjnvJNqVnB5hHCow3VQ=", + "dev": true + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.1.tgz", - "integrity": "sha512-Fg1xXAv+qXKdwHiJFMcZSqsMcbPlkzsZtf8KkLJ2fqnP+lqg2RjEKgDcSfO9CO1+p4LZKgApDBUUUqKaaRhwZQ==", - "dev": true, - "requires": { - "is-extendable": "1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } + "extend-shallow": "3.0.2" } }, "sprintf-js": { @@ -8803,28 +10941,10 @@ } } }, - "ssh2": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.5.4.tgz", - "integrity": "sha1-G/a2soyW6u8mf01sRqWiUXpZnic=", - "requires": { - "ssh2-streams": "0.1.20" - } - }, - "ssh2-streams": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.1.20.tgz", - "integrity": "sha1-URGNFUVV31Rp7h9n4M8efoosDjo=", - "requires": { - "asn1": "0.2.3", - "semver": "5.4.1", - "streamsearch": "0.1.2" - } - }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -8836,6 +10956,15 @@ "tweetnacl": "0.14.5" } }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -8860,6 +10989,46 @@ "is-descriptor": "0.1.6" } }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -8890,7 +11059,7 @@ "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", "dev": true, "requires": { - "readable-stream": "2.3.3" + "readable-stream": "2.3.5" } }, "stream-browserify": { @@ -8900,22 +11069,38 @@ "dev": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.3" + "readable-stream": "2.3.5" + } + }, + "stream-each": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", + "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" } }, "stream-http": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", - "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.1.tgz", + "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", "dev": true, "requires": { "builtin-status-codes": "3.0.0", "inherits": "2.0.3", - "readable-stream": "2.3.3", + "readable-stream": "2.3.5", "to-arraybuffer": "1.0.1", "xtend": "4.0.1" } }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", @@ -8931,7 +11116,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, "requires": { "safe-buffer": "5.1.1" } @@ -8985,15 +11169,10 @@ "get-stdin": "4.0.1" } }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, "style-loader": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.18.2.tgz", - "integrity": "sha512-WPpJPZGUxWYHWIUMNNOYqql7zh85zGmr84FdTVWq52WTIkqlW9xSxD3QYWi/T31cqn9UNSsietVEgGn2aaSCzw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.19.1.tgz", + "integrity": "sha512-IRE+ijgojrygQi3rsqT0U4dd+UcPCqcVvauZpCnQrGAlEe+FUIyrK93bUDScamesjP08JlQNsFJU+KmPedP5Og==", "dev": true, "requires": { "loader-utils": "1.1.0", @@ -9041,6 +11220,11 @@ } } }, + "swagger-ui-express": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-2.0.15.tgz", + "integrity": "sha1-+6haNXtAwNYp6UJTcZmYe4CFWH0=" + }, "symbol-observable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", @@ -9086,10 +11270,20 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "2.3.5", + "xtend": "4.0.1" + } + }, "thunky": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-0.1.0.tgz", - "integrity": "sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz", + "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=", "dev": true }, "time-stamp": { @@ -9099,9 +11293,9 @@ "dev": true }, "timers-browserify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", - "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", + "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", "dev": true, "requires": { "setimmediate": "1.0.5" @@ -9128,48 +11322,21 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "1.1.6" } } } }, "to-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.1.tgz", - "integrity": "sha1-FTWL7kosg712N3uh3ASdDxiDeq4=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "regex-not": "1.0.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" } }, "to-regex-range": { @@ -9194,9 +11361,9 @@ "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { "punycode": "1.4.1" } @@ -9238,50 +11405,59 @@ "dev": true }, "tslib": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.1.tgz", - "integrity": "sha1-aUavLR1lGnsYY7Ux1uWvpBqkTqw=", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", "dev": true }, "tslint": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.8.0.tgz", - "integrity": "sha1-H0mtWy53x2w69N3K5VKuTjYS6xM=", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", + "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", "dev": true, "requires": { "babel-code-frame": "6.26.0", "builtin-modules": "1.1.1", "chalk": "2.3.0", - "commander": "2.11.0", - "diff": "3.4.0", + "commander": "2.15.1", + "diff": "3.5.0", "glob": "7.1.2", + "js-yaml": "3.11.0", "minimatch": "3.0.4", "resolve": "1.4.0", - "semver": "5.4.1", - "tslib": "1.8.1", - "tsutils": "2.13.0" + "semver": "5.5.0", + "tslib": "1.9.0", + "tsutils": "2.22.2" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + } } }, "tslint-loader": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.5.3.tgz", - "integrity": "sha1-ND90Ei2U81a2iUV9P1n2SmmrYG8=", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.6.0.tgz", + "integrity": "sha512-Me9Qf/87BOfCY8uJJw+J7VMF4U8WiMXKLhKKKugMydF0xMhMOt9wo2mjYTNhwbF9H7SHh8PAIwRG8roisTNekQ==", "dev": true, "requires": { "loader-utils": "1.1.0", "mkdirp": "0.5.1", "object-assign": "4.1.1", "rimraf": "2.6.2", - "semver": "5.4.1" + "semver": "5.5.0" } }, "tsutils": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.13.0.tgz", - "integrity": "sha512-FuWzNJbMsp3gcZMbI3b5DomhW4Ia41vMxjN63nKWI0t7f+I3UmHfRl0TrXJTwI2LUduDG+eR1Mksp3pvtlyCFQ==", + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.22.2.tgz", + "integrity": "sha512-u06FUSulCJ+Y8a2ftuqZN6kIGqdP2yJjUPEngXqmdPND4UQfb04igcotH+dw+IFr417yP6muCLE8/5/Qlfnx0w==", "dev": true, "requires": { - "tslib": "1.8.1" + "tslib": "1.9.0" } }, "tty-browserify": { @@ -9298,31 +11474,6 @@ "safe-buffer": "5.1.1" } }, - "tunnel-ssh": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-4.1.3.tgz", - "integrity": "sha1-kRIWrXnfEAkILFcT/tJkdu0jm60=", - "requires": { - "debug": "2.6.0", - "lodash.defaults": "4.2.0", - "ssh2": "0.5.4" - }, - "dependencies": { - "debug": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", - "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" - } - } - }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -9338,11 +11489,60 @@ "mime-types": "2.1.17" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "typescript": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", - "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", - "dev": true + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==" + }, + "typescript-rest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/typescript-rest/-/typescript-rest-1.3.0.tgz", + "integrity": "sha1-t/KAcRKXsQIJ5qIDDw3J7wNcvyI=", + "requires": { + "@types/body-parser": "0.0.33", + "@types/cookie-parser": "1.4.1", + "@types/express": "4.11.1", + "@types/express-serve-static-core": "4.0.57", + "@types/multer": "0.0.32", + "@types/serve-static": "1.13.1", + "body-parser": "1.18.2", + "cookie-parser": "1.4.3", + "cors": "2.8.4", + "express": "4.16.3", + "fs-extra": "2.1.2", + "lodash": "4.17.5", + "multer": "1.3.0", + "path": "0.12.7", + "reflect-metadata": "0.1.12", + "require-glob": "3.2.0", + "swagger-ui-express": "2.0.15", + "yamljs": "0.2.10" + }, + "dependencies": { + "@types/body-parser": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-0.0.33.tgz", + "integrity": "sha1-M8oUmPw35Rxd8MgcrjRWnnBB4CU=", + "requires": { + "@types/express": "4.11.1" + } + }, + "@types/express": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.11.1.tgz", + "integrity": "sha512-ttWle8cnPA5rAelauSWeWJimtY2RsUf2aspYZs7xPHiWgOlPn6nnUfBMtrkcnjFJuIHJF4gNOdVvpLK2Zmvh6g==", + "requires": { + "@types/body-parser": "0.0.33", + "@types/express-serve-static-core": "4.0.57", + "@types/serve-static": "1.13.1" + } + } + } }, "uglify-js": { "version": "3.0.28", @@ -9400,12 +11600,6 @@ "yargs": "3.10.0" } }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", @@ -9420,6 +11614,17 @@ } } }, + "umzug": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.1.0.tgz", + "integrity": "sha512-BgT+ekpItEWaG+3JjLLj6yVTxw2wIH8Cr6JyKYIzukWAx9nzGhC6BGHb/IRMjpobMM1qtIrReATwLUjKpU2iOQ==", + "requires": { + "babel-runtime": "6.26.0", + "bluebird": "3.5.1", + "lodash": "4.17.5", + "resolve": "1.4.0" + } + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -9432,6 +11637,15 @@ "set-value": "0.4.3" }, "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, "set-value": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", @@ -9467,6 +11681,24 @@ "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", "dev": true }, + "unique-filename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", + "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", + "dev": true, + "requires": { + "unique-slug": "2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", + "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", + "dev": true, + "requires": { + "imurmurhash": "0.1.4" + } + }, "units-css": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/units-css/-/units-css-0.4.0.tgz", @@ -9522,6 +11754,12 @@ } } }, + "upath": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", + "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", + "dev": true + }, "upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", @@ -9551,13 +11789,22 @@ } }, "url-loader": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.5.9.tgz", - "integrity": "sha512-B7QYFyvv+fOBqBVeefsxv6koWWtjmHaMFT6KZWti4KRw8YUD/hOU+3AECvXuzyVawIBx3z7zQRejXCDSO5kk1Q==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.6.2.tgz", + "integrity": "sha512-h3qf9TNn53BpuXTTcpC+UehiRrl0Cv45Yr/xWayApjw6G8Bg2dGke7rIwDQ39piciWCWrC+WiqLjOh3SUp9n0Q==", "dev": true, "requires": { "loader-utils": "1.1.0", - "mime": "1.3.4" + "mime": "1.6.0", + "schema-utils": "0.3.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + } } }, "url-parse": { @@ -9579,49 +11826,18 @@ } }, "use": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/use/-/use-2.0.2.tgz", - "integrity": "sha1-riig1y+TvyJCKhii43mZMRLeyOg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", "dev": true, "requires": { - "define-property": "0.2.5", - "isobject": "3.0.1", - "lazy-cache": "2.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "kind-of": "6.0.2" } }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, "requires": { "inherits": "2.0.1" }, @@ -9629,16 +11845,14 @@ "inherits": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" } } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utila": { "version": "0.4.0", @@ -9646,43 +11860,30 @@ "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", "dev": true }, - "utile": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", - "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", - "requires": { - "async": "0.9.2", - "deep-equal": "0.2.2", - "i": "0.3.6", - "mkdirp": "0.5.1", - "ncp": "1.0.1", - "rimraf": "2.6.2" - } - }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" } }, "validator": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-9.2.0.tgz", - "integrity": "sha512-6Ij4Eo0KM4LkR0d0IegOwluG5453uqT5QyF5SV5Ezvm8/zmkKI/L4eoraafZGlZPC9guLkwKzgypcw8VGWWnGA==" + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==" }, "vary": { "version": "1.1.2", @@ -9721,46 +11922,35 @@ } }, "watchpack": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", - "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.5.0.tgz", + "integrity": "sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==", "dev": true, "requires": { - "async": "2.6.0", - "chokidar": "1.7.0", - "graceful-fs": "4.1.11" - }, - "dependencies": { - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - } + "chokidar": "2.0.2", + "graceful-fs": "4.1.11", + "neo-async": "2.5.0" } }, "wbuf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz", - "integrity": "sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "requires": { "minimalistic-assert": "1.0.0" } }, "webpack": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz", - "integrity": "sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.11.0.tgz", + "integrity": "sha512-3kOFejWqj5ISpJk4Qj/V7w98h9Vl52wak3CLiw/cDOfbVTq7FeoZ0SdoHHY9PYlHr50ZS42OfvzE2vB4nncKQg==", "dev": true, "requires": { - "acorn": "5.2.1", + "acorn": "5.5.3", "acorn-dynamic-import": "2.0.2", - "ajv": "5.5.1", - "ajv-keywords": "2.1.1", + "ajv": "6.3.0", + "ajv-keywords": "3.1.0", "async": "2.6.0", "enhanced-resolve": "3.4.1", "escope": "3.6.0", @@ -9776,7 +11966,7 @@ "supports-color": "4.2.1", "tapable": "0.2.8", "uglifyjs-webpack-plugin": "0.4.6", - "watchpack": "1.4.0", + "watchpack": "1.5.0", "webpack-sources": "1.1.0", "yargs": "8.0.2" }, @@ -9787,15 +11977,6 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -9814,15 +11995,6 @@ "tapable": "0.2.8" } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, "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", @@ -9867,6 +12039,12 @@ "pify": "2.3.0" } }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -9919,6 +12097,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, "yargs": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", @@ -9973,36 +12157,36 @@ } }, "webpack-dev-server": { - "version": "2.9.7", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.9.7.tgz", - "integrity": "sha512-Pu7uoQFgQj5RE5wmlfkpYSzihMKxulwEuO2xCsaMnAnyRSApwoVi3B8WCm9XbigyWTHaIMzYGkB90Vr6leAeTQ==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.2.tgz", + "integrity": "sha512-zrPoX97bx47vZiAXfDrkw8pe9QjJ+lunQl3dypojyWwWr1M5I2h0VSrMPfTjopHQPRNn+NqfjcMmhoLcUJe2gA==", "dev": true, "requires": { "ansi-html": "0.0.7", "array-includes": "3.0.3", "bonjour": "3.5.0", - "chokidar": "1.7.0", - "compression": "1.7.1", + "chokidar": "2.0.2", + "compression": "1.7.2", "connect-history-api-fallback": "1.5.0", "debug": "3.1.0", "del": "3.0.0", - "express": "4.16.2", + "express": "4.16.3", "html-entities": "1.2.1", "http-proxy-middleware": "0.17.4", - "import-local": "0.1.1", + "import-local": "1.0.0", "internal-ip": "1.2.0", "ip": "1.1.5", "killable": "1.0.0", - "loglevel": "1.6.0", - "opn": "5.1.0", + "loglevel": "1.6.1", + "opn": "5.3.0", "portfinder": "1.0.13", - "selfsigned": "1.10.1", + "selfsigned": "1.10.2", "serve-index": "1.9.1", - "sockjs": "0.3.18", + "sockjs": "0.3.19", "sockjs-client": "1.1.4", "spdy": "3.4.7", "strip-ansi": "3.0.1", - "supports-color": "4.2.1", + "supports-color": "5.3.0", "webpack-dev-middleware": "1.12.2", "yargs": "6.6.0" }, @@ -10022,6 +12206,27 @@ "ms": "2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, "yargs": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", @@ -10078,7 +12283,7 @@ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "http-parser-js": "0.4.9", + "http-parser-js": "0.4.11", "websocket-extensions": "0.1.3" } }, @@ -10088,11 +12293,6 @@ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, - "when": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/when/-/when-2.0.1.tgz", - "integrity": "sha1-jYcv4V5oQkyRtLck6EjggH2rZkI=" - }, "whet.extend": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", @@ -10155,17 +12355,18 @@ } }, "wkx": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.2.tgz", - "integrity": "sha1-d201pjSlwi5lbkdEvetU+D/Szo0=", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.4.tgz", + "integrity": "sha512-eVVHka2jRaAp9QanKhLpxWs3AGDV0b8cijlavxBnn4ryXzq5N/3Xe3nkQsI0XMRA16RURwviCWuOCj4mXCmrxw==", "requires": { - "@types/node": "6.0.92" + "@types/node": "8.9.5" } }, "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true }, "wrap-ansi": { "version": "2.1.0", @@ -10191,13 +12392,12 @@ "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { @@ -10206,6 +12406,15 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, + "yamljs": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.2.10.tgz", + "integrity": "sha1-SBzHwlynOvWfWR8MluPOVsdXpA8=", + "requires": { + "argparse": "1.0.9", + "glob": "7.1.2" + } + }, "yargs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", @@ -10232,6 +12441,12 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true } } }, @@ -10253,9 +12468,9 @@ } }, "zone.js": { - "version": "0.8.18", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.18.tgz", - "integrity": "sha512-knKOBQM0oea3/x9pdyDuDi7RhxDlJhOIkeixXSiTKWLgs4LpK37iBc+1HaHwzlciHUKT172CymJFKo8Xgh+44Q==", + "version": "0.8.20", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.20.tgz", + "integrity": "sha512-FXlA37ErSXCMy5RNBcGFgCI/Zivqzr0D19GuvDxhcYIJc7xkFp6c29DKyODJu0Zo+EMyur/WPPgcBh1EHjB9jA==", "dev": true } } diff --git a/package.json b/package.json index d2479eb..1e470bc 100644 --- a/package.json +++ b/package.json @@ -2,96 +2,107 @@ "name": "matrix-dimension", "version": "1.0.0", "description": "An alternative integrations manager for Riot", - "main": "app.js", + "main": "build/app/index.js", "license": "GPL-3.0", "scripts": { - "dev": "webpack-dev-server --inline --progress --port 8080 --host 0.0.0.0", - "build": "rimraf web-dist && webpack --progress --profile --bail" + "start:web": "webpack-dev-server --inline --progress --port 8080 --host 0.0.0.0", + "start:app": "npm run-script build && node build/app/index.js", + "build": "npm run-script build:web && npm run-script build:app", + "build:web": "rimraf build/web && webpack --progress --profile --bail", + "build:app": "rimraf build/app && tsc -p tsconfig-app.json", + "lint": "npm run-script lint:app && npm run-script lint:web", + "lint:app": "tslint --project ./tsconfig-app.json -t stylish", + "lint:web": "tslint --project ./tsconfig.json -t stylish" }, "repository": { "type": "git", "url": "git+https://github.com/turt2live/matrix-dimension.git" }, - "greenkeeper": { - "ignore": [ - "@types/node" - ] - }, "author": "Travis Ralston", "dependencies": { - "bluebird": "^3.5.1", + "@types/body-parser": "^1.16.8", + "@types/node": "^8.9.5", "body-parser": "^1.18.2", - "chalk": "^2.3.0", - "config": "^1.28.1", - "db-migrate": "^0.10.2", - "db-migrate-sqlite3": "^0.2.1", + "config": "^1.30.0", "dns-then": "^0.1.0", - "embed-video": "^2.0.0", - "express": "^4.16.2", - "js-yaml": "^3.10.0", - "lodash": "^4.17.4", - "matrix-js-sdk": "^0.8.5", - "moment": "^2.19.3", + "express": "^4.16.3", + "git-rev-sync": "^1.10.0", + "js-yaml": "^3.11.0", + "lodash": "^4.17.5", + "matrix-js-sdk": "^0.9.2", + "matrix-js-snippets": "^0.2.5", + "memory-cache": "^0.2.0", + "mime": "^2.2.0", + "moment": "^2.21.0", "netmask": "^1.0.6", "random-string": "^0.2.0", - "request": "^2.83.0", - "screenfull": "^3.3.2", - "sequelize": "^4.27.0", + "request": "^2.85.0", + "require-dir-all": "^0.4.15", + "sequelize": "^4.37.3", + "sequelize-typescript": "^0.6.3", "sqlite3": "^3.1.13", - "url": "^0.11.0", - "winston": "^2.4.0" + "typescript": "^2.7.2", + "typescript-rest": "^1.3.0", + "umzug": "^2.1.0", + "url": "^0.11.0" }, "devDependencies": { - "@angular/animations": "^5.0.0", - "@angular/common": "^5.0.0", - "@angular/compiler": "^5.0.0", - "@angular/core": "^5.0.0", - "@angular/forms": "^5.0.0", - "@angular/http": "^5.0.0", - "@angular/platform-browser": "^5.0.0", - "@angular/platform-browser-dynamic": "^5.0.0", - "@angular/router": "^5.0.0", + "@angular/animations": "^5.2.9", + "@angular/common": "^5.2.9", + "@angular/compiler": "^5.2.9", + "@angular/core": "^5.2.9", + "@angular/forms": "^5.2.9", + "@angular/http": "^5.2.9", + "@angular/platform-browser": "^5.2.9", + "@angular/platform-browser-dynamic": "^5.2.9", + "@angular/router": "^5.2.9", "@angularclass/hmr": "^2.1.0", "@angularclass/hmr-loader": "^3.0.2", - "@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.7", - "@types/jquery": "^3.2.16", - "@types/node": "^6.0.92", + "@fortawesome/fontawesome": "^1.1.4", + "@fortawesome/fontawesome-free-brands": "^5.0.8", + "@fortawesome/fontawesome-free-regular": "^5.0.8", + "@fortawesome/fontawesome-free-solid": "^5.0.8", + "@ng-bootstrap/ng-bootstrap": "^1.0.2", + "@types/jquery": "^3.3.1", "angular2-template-loader": "^0.6.2", - "angular2-toaster": "^4.0.0", + "angular2-toaster": "^4.0.2", "angular2-ui-switch": "^1.2.0", - "awesome-typescript-loader": "^3.4.1", - "codelyzer": "^3.2.2", - "copy-webpack-plugin": "^4.2.3", - "core-js": "^2.5.2", - "css-loader": "^0.28.7", + "awesome-typescript-loader": "^3.5.0", + "codelyzer": "^4.2.1", + "copy-webpack-plugin": "^4.5.1", + "core-js": "^2.5.3", + "css-loader": "^0.28.11", "cssnano": "^3.10.0", + "embed-video": "^2.0.0", "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "^1.1.5", + "file-loader": "^1.1.11", "goby": "^1.1.2", - "html-loader": "^0.5.1", + "html-loader": "^0.5.5", "html-webpack-plugin": "^2.28.0", - "jquery": "^3.2.1", + "jquery": "^3.3.1", "json-loader": "^0.5.4", - "ngx-modialog": "^3.0.4", - "node-sass": "^4.7.2", - "postcss-cssnext": "^3.0.0", - "postcss-import": "^10.0.0", - "postcss-loader": "^2.0.9", - "postcss-scss": "^1.0.0", + "ng2-breadcrumbs": "^0.1.281", + "ngx-modialog": "^5.0.0", + "node-sass": "^4.8.3", + "postcss-cssnext": "^3.1.0", + "postcss-import": "^11.1.0", + "postcss-loader": "^2.1.3", + "postcss-scss": "^1.0.4", "raw-loader": "^0.5.1", - "reflect-metadata": "^0.1.10", + "reflect-metadata": "^0.1.12", "rimraf": "^2.6.2", - "rxjs": "^5.5.5", - "sass-loader": "^6.0.3", + "rxjs": "^5.5.7", + "sass-loader": "^6.0.7", + "screenfull": "^3.3.2", "shelljs": "^0.7.8", - "style-loader": "^0.18.2", + "spinkit": "^1.2.5", + "style-loader": "^0.19.1", "ts-helpers": "^1.1.2", - "tslint": "^5.8.0", - "tslint-loader": "^3.4.3", - "typescript": "^2.6.2", - "url-loader": "^0.5.8", - "webpack": "^3.10.0", - "webpack-dev-server": "^2.9.7", - "zone.js": "^0.8.18" + "tslint": "^5.9.1", + "tslint-loader": "^3.6.0", + "url-loader": "^0.6.2", + "webpack": "^3.11.0", + "webpack-dev-server": "^2.11.2", + "zone.js": "^0.8.20" } } diff --git a/src/Dimension.js b/src/Dimension.js deleted file mode 100644 index 3914722..0000000 --- a/src/Dimension.js +++ /dev/null @@ -1,67 +0,0 @@ -var express = require("express"); -var config = require("config"); -var log = require("./util/LogService"); -var DimensionStore = require("./storage/DimensionStore"); -var bodyParser = require('body-parser'); -var path = require("path"); -var DimensionApi = require("./DimensionApi"); -var ScalarApi = require("./ScalarApi"); -var IRCApi = require("./integration/impl/irc/IRCApi"); -var URL = require("url"); - -// TODO: Convert backend to typescript? Would avoid stubbing classes all over the place - -/** - * Primary entry point for Dimension - */ -class Dimension { - - /** - * Creates a new Dimension - */ - constructor() { - } - - /** - * Starts the Dimension service - * @param {DimensionStore} db the store to use - */ - start(db) { - this._db = db; - this._app = express(); - this._app.use(express.static('web-dist')); - this._app.use(bodyParser.json()); - - // Register routes for angular app - this._app.get(['/riot', '/riot/*', '/widgets', '/widgets/*'], (req, res) => { - res.sendFile(path.join(__dirname, "..", "web-dist", "index.html")); - }); - - // Allow CORS - this._app.use((req, res, next) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); - }); - - // Logging incoming requests - this._app.use((req, res, next) => { - var parsedUrl = URL.parse(req.url, true); - if (parsedUrl.query && parsedUrl.query["scalar_token"]) { - parsedUrl.query["scalar_token"] = "redacted"; - parsedUrl.search = undefined; // to trigger the URL.format to use `query` - } - log.verbose("Dimension", "Incoming: " + req.method + " " + URL.format(parsedUrl)); - next(); - }); - - DimensionApi.bootstrap(this._app, this._db); - ScalarApi.bootstrap(this._app, this._db); - IRCApi.bootstrap(this._app, this._db); - - this._app.listen(config.get('web.port'), config.get('web.address')); - log.info("Dimension", "API and UI listening on " + config.get("web.address") + ":" + config.get("web.port")); - } -} - -module.exports = new Dimension(); \ No newline at end of file diff --git a/src/DimensionApi.js b/src/DimensionApi.js deleted file mode 100644 index c8b4728..0000000 --- a/src/DimensionApi.js +++ /dev/null @@ -1,287 +0,0 @@ -const IntegrationImpl = require("./integration/impl/index"); -const Integrations = require("./integration/index"); -const _ = require("lodash"); -const log = require("./util/LogService"); -const request = require("request"); -const dns = require("dns-then"); -const urlParse = require("url"); -const Netmask = require("netmask").Netmask; -const config = require("config"); - -/** - * API handler for the Dimension API - */ -class DimensionApi { - - /** - * Creates a new Dimension API - */ - constructor() { - } - - /** - * Bootstraps the Dimension API - * @param {*} app the Express application - * @param {DimensionStore} db the store to use - */ - bootstrap(app, db) { - this._db = db; - - app.get("/api/v1/dimension/integrations/:roomId", this._getIntegrations.bind(this)); - app.delete("/api/v1/dimension/integrations/:roomId/:type/:integrationType", this._removeIntegration.bind(this)); - app.put("/api/v1/dimension/integrations/:roomId/:type/:integrationType/state", this._updateIntegrationState.bind(this)); - app.get("/api/v1/dimension/integrations/:roomId/:type/:integrationType/state", this._getIntegrationState.bind(this)); - app.get("/api/v1/dimension/widgets/embeddable", this._checkEmbeddable.bind(this)); - app.get("/api/v1/dimension/integration/:type/:integrationType", this._getIntegration.bind(this)); - } - - _checkEmbeddable(req, res) { - // Unauthed endpoint. - - var url = req.query.url; - var parts = urlParse.parse(url); - var processed = false; - - // Only allow http and https - if (parts.protocol !== "http:" && parts.protocol !== "https:") { - res.status(400).send({error: "Invalid request scheme " + parts.protocol, canEmbed: false}); - processed = true; - return; - } - - // Verify the address is permitted for widgets - var hostname = parts.hostname.split(":")[0]; - dns.resolve4(hostname).then(addresses => { - log.verbose("DimensionApi", "Hostname " + hostname + " resolves to " + addresses); - if (addresses.length == 0) { - res.status(400).send({error: "Unrecongized address", canEmbed: false}); - processed = true; - return; - } - for (var ipOrCidr of config.get("widgetBlacklist")) { - var block = new Netmask(ipOrCidr); - for (var address of addresses) { - if (block.contains(address)) { - res.status(400).send({error: "Address not allowed", canEmbed: false}); - processed = true; - return; - } - } - } - }, err => { - log.verbose("DimensionApi", "Error resolving host " + hostname); - log.verbose("DimensionApi", err); - - res.status(400).send({error: "DNS error", canEmbed: false}); - processed = true; - }).then(() => { - if (processed) return; - - // Verify that the content can actually be embedded (CORS) - request(url, (err, response) => { - if (err) { - log.verbose("DimensionApi", "Error contacting host " + hostname); - log.verbose("DimensionApi", err); - - res.status(400).send({error: "Host error", canEmbed: false}); - return; - } - - if (response.statusCode >= 200 && response.statusCode < 300) { - // 200 OK - var headers = response.headers; - var xFrameOptions = (headers['x-frame-options'] || '').toLowerCase(); - - if (xFrameOptions === 'sameorigin' || xFrameOptions === 'deny') { - res.status(400).send({error: "X-Frame-Options forbids embedding", canEmbed: false}); - } else res.status(200).send({canEmbed: true}); - } else { - res.status(400).send({error: "Unsuccessful status code: " + response.statusCode, canEmbed: false}); - } - }); - }); - } - - _findIntegration(integrationConfig, roomId, scalarToken) { - var factory = IntegrationImpl.getFactory(integrationConfig); - if (!factory) throw new Error("Missing config factory for " + integrationConfig.name); - - try { - return factory(this._db, integrationConfig, roomId, scalarToken); - } catch (err) { - throw new Error("Error using factory for " + integrationConfig.name + ". Please either fix the integration settings or disable the integration.", err); - } - } - - _getIntegration(req, res) {res.setHeader("Content-Type", "application/json"); - // Unauthed endpoint. - - var type = req.params.type; - var integrationType = req.params.integrationType; - - if (!type || !integrationType) { - res.status(400).send({error: "Missing integration type or type"}); - return; - } - - var byIntegrationType = Integrations.byType[type]; - if (!byIntegrationType || !byIntegrationType[integrationType]) { - res.status(400).send({error: "Unknown integration"}); - return; - } - var integrationConfig = byIntegrationType[integrationType]; - - res.status(200).send(integrationConfig); - } - - _getIntegrations(req, res) { - res.setHeader("Content-Type", "application/json"); - - var roomId = req.params.roomId; - if (!roomId) { - res.status(400).send({error: 'Missing room ID'}); - return; - } - - var scalarToken = req.query.scalar_token; - this._db.checkToken(scalarToken).then(() => { - var integrations = _.map(Integrations.all, i => JSON.parse(JSON.stringify(i))); // clone - - var promises = []; - var remove = []; - _.forEach(integrations, integration => { - try { - promises.push(this._findIntegration(integration, roomId, scalarToken).then(builtIntegration => { - return builtIntegration.getState().then(state => { - var keys = _.keys(state); - for (var key of keys) { - integration[key] = state[key]; - } - - return builtIntegration.getUserId(); - }).then(userId => { - integration.userId = userId; - }); - })); - } catch (err) { - remove.push(integration); - log.error("DimensionApi", err); - } - }); - - for (var toRemove of remove) { - var idx = integrations.indexOf(toRemove); - if (idx === -1) continue; - log.warn("DimensionApi", "Disabling integration " + toRemove.name + " due to an error encountered in setup"); - integrations.splice(idx, 1); - } - - Promise.all(promises).then(() => res.send(_.map(integrations, integration => { - // Remove sensitive material - integration.upstream = undefined; - integration.hosted = undefined; - return integration; - }))); - }).catch(err => { - log.error("DimensionApi", err); - console.error(err); - res.status(500).send({error: err}); - }); - } - - _removeIntegration(req, res) { - var roomId = req.params.roomId; - var scalarToken = req.query.scalar_token; - var type = req.params.type; - var integrationType = req.params.integrationType; - - if (!roomId || !scalarToken || !type || !integrationType) { - res.status(400).send({error: "Missing room, integration type, type, or token"}); - return; - } - - var integrationConfig = Integrations.byType[type][integrationType]; - if (!integrationConfig) { - res.status(400).send({error: "Unknown integration"}); - return; - } - - log.info("DimensionApi", "Remove requested for " + type + " (" + integrationType + ") in room " + roomId); - - this._db.checkToken(scalarToken).then(() => { - return this._findIntegration(integrationConfig, roomId, scalarToken); - }).then(integration => integration.removeFromRoom(roomId)).then(() => { - res.status(200).send({success: true}); - }).catch(err => { - log.error("DimensionApi", err); - console.error(err); - res.status(500).send({error: err.message}); - }); - } - - _updateIntegrationState(req, res) { - var roomId = req.params.roomId; - var scalarToken = req.body.scalar_token; - var type = req.params.type; - var integrationType = req.params.integrationType; - - if (!roomId || !scalarToken || !type || !integrationType) { - res.status(400).send({error: "Missing room, integration type, type, or token"}); - return; - } - - var integrationConfig = Integrations.byType[type][integrationType]; - if (!integrationConfig) { - res.status(400).send({error: "Unknown integration"}); - return; - } - - log.info("DimensionApi", "Update state requested for " + type + " (" + integrationType + ") in room " + roomId); - - this._db.checkToken(scalarToken).then(() => { - return this._findIntegration(integrationConfig, roomId, scalarToken); - }).then(integration => { - return integration.updateState(req.body.state); - }).then(newState => { - res.status(200).send(newState); - }).catch(err => { - log.error("DimensionApi", err); - console.error(err); - res.status(500).send({error: err.message}); - }); - } - - _getIntegrationState(req, res) { - var roomId = req.params.roomId; - var scalarToken = req.query.scalar_token; - var type = req.params.type; - var integrationType = req.params.integrationType; - - if (!roomId || !scalarToken || !type || !integrationType) { - res.status(400).send({error: "Missing room, integration type, type, or token"}); - return; - } - - var integrationConfig = Integrations.byType[type][integrationType]; - if (!integrationConfig) { - res.status(400).send({error: "Unknown integration"}); - return; - } - - log.info("DimensionApi", "State requested for " + type + " (" + integrationType + ") in room " + roomId); - - this._db.checkToken(scalarToken).then(() => { - return this._findIntegration(integrationConfig, roomId, scalarToken); - }).then(integration => { - return integration.getState(); - }).then(state => { - res.status(200).send(state); - }).catch(err => { - log.error("DimensionApi", err); - console.error(err); - res.status(500).send({error: err.message}); - }); - } -} - -module.exports = new DimensionApi(); diff --git a/src/MemoryCache.ts b/src/MemoryCache.ts new file mode 100644 index 0000000..e66881a --- /dev/null +++ b/src/MemoryCache.ts @@ -0,0 +1,50 @@ +import * as memoryCache from "memory-cache"; +import { LogService } from "matrix-js-snippets"; + +export class MemoryCache { + + private internalCache = new memoryCache.Cache(); + + constructor() { + } + + public put(key: string, value: any, timeoutMs?: number): void { + this.internalCache.put(key, value, timeoutMs); + } + + public get(key: string): any { + return this.internalCache.get(key); + } + + public del(key: string): void { + this.internalCache.del(key); + } + + public clear(): void { + this.internalCache.clear(); + } +} + +class _CacheManager { + private caches: { [namespace: string]: MemoryCache } = {}; + + public for(namespace: string): MemoryCache { + let cache = this.caches[namespace]; + if (!cache) { + LogService.info("MemoryCache", "Creating a new cache for namespace: " + namespace); + cache = this.caches[namespace] = new MemoryCache(); + } + + return cache; + } +} + +export const Cache = new _CacheManager(); + +export const CACHE_INTEGRATIONS = "integrations"; +export const CACHE_NEB = "neb"; +export const CACHE_UPSTREAM = "upstream"; +export const CACHE_SCALAR_ACCOUNTS = "scalar-accounts"; +export const CACHE_WIDGET_TITLES = "widget-titles"; +export const CACHE_FEDERATION = "federation"; +export const CACHE_IRC_BRIDGE = "irc-bridge"; \ No newline at end of file diff --git a/src/OpenID.js b/src/OpenID.js deleted file mode 100644 index 75e9d12..0000000 --- a/src/OpenID.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Serves the purpose of being a documentation endpoint - */ -class OpenID { - access_token = ""; - token_type = ""; - matrix_server_name = ""; - expires_in = 0; -} - -module.exports = OpenID; \ No newline at end of file diff --git a/src/ScalarApi.js b/src/ScalarApi.js deleted file mode 100644 index 528bca1..0000000 --- a/src/ScalarApi.js +++ /dev/null @@ -1,115 +0,0 @@ -var MatrixLiteClient = require("./matrix/MatrixLiteClient"); -var randomString = require("random-string"); -var ScalarClient = require("./scalar/ScalarClient.js"); -var _ = require("lodash"); -var log = require("./util/LogService"); -var Promise = require("bluebird"); -var UpstreamConfiguration = require("./UpstreamConfiguration"); - -/** - * API handler for the Scalar API, as required by Riot - */ -class ScalarApi { - - /** - * Creates a new Scalar API - */ - constructor() { - } - - /** - * Bootstraps the Scalar API - * @param {*} app the Express application - * @param {DimensionStore} db the store to use - */ - bootstrap(app, db) { - this._db = db; - - app.post("/api/v1/scalar/register", this._scalarRegister.bind(this)); - app.get("/api/v1/scalar/checkToken", this._checkScalarToken.bind(this)); - app.get("/api/v1/scalar/widgets/title_lookup", this._getWidgetTitle.bind(this)); - } - - _getWidgetTitle(req, res) { - res.setHeader("Content-Type", "application/json"); - - var token = req.query.scalar_token; - var url = req.query.curl; - - if (!token || !url) { - res.status(400).send({error: "Missing token or curl"}); - return; - } - - this._db.checkToken(token).then(() => { - MatrixLiteClient.getUrlPreview(url).then(preview => { - if (!preview["og:title"]) { - res.status(404).send({error:{message:"Could not locate a title for the URL"}}); - return; - } - - // We need to convert the preview response to what Scalar expects - res.status(200).send({ - cached_response: false, - page_title_cache_item: { - expires: null, // unused - cached_response_err: null, // unused - cached_title: preview["og:title"], - } - }); - }).catch(err => { - res.status(500).send({error: {message: "Failed to get preview"}}); - log.error("ScalarApi", "Failed to get URL preview"); - log.error("ScalarApi", err); - }); - }).catch(err => { - res.status(401).send({error: {message: "Failed to authenticate token"}}); - log.warn("ScalarApi", "Failed to authenticate token"); - log.warn("ScalarApi", err); - }); - } - - _checkScalarToken(req, res) { - var token = req.query.scalar_token; - if (!token) res.sendStatus(400); - else this._db.checkToken(token).then(() => { - res.sendStatus(200); - }).catch(e => { - res.sendStatus(401); - log.warn("ScalarApi", "Failed to authenticate token"); - log.verbose("ScalarApi", e); - }); - } - - _scalarRegister(req, res) { - res.setHeader("Content-Type", "application/json"); - - var tokenInfo = req.body; - if (!tokenInfo || !tokenInfo['access_token'] || !tokenInfo['token_type'] || !tokenInfo['matrix_server_name'] || !tokenInfo['expires_in']) { - res.status(400).send({error: 'Missing OpenID'}); - return; - } - - var client = new MatrixLiteClient(tokenInfo); - var scalarToken = randomString({length: 25}); - var userId; - client.getSelfMxid().then(mxid => { - userId = mxid; - if (!mxid) throw new Error("Token does not resolve to a matrix user"); - - // TODO: Make this part more generic for other upstreams (#22) - if (!UpstreamConfiguration.hasUpstream("vector")) return Promise.resolve(null); - return ScalarClient.register(tokenInfo); - }).then(upstreamToken => { - return this._db.createToken(userId, tokenInfo, scalarToken, upstreamToken); - }).then(() => { - res.setHeader("Content-Type", "application/json"); - res.send({scalar_token: scalarToken}); - }).catch(err => { - log.error("ScalarApi", err); - res.status(500).send({error: err.message}); - }); - } -} - -module.exports = new ScalarApi(); \ No newline at end of file diff --git a/src/UpstreamConfiguration.js b/src/UpstreamConfiguration.js deleted file mode 100644 index de22466..0000000 --- a/src/UpstreamConfiguration.js +++ /dev/null @@ -1,50 +0,0 @@ -var LogService = require("./util/LogService"); -var _ = require("lodash"); -var config = require("config"); - -/** - * Handles all upstream configuration information, such as URLs, tokens, and whether or not they are enabled. - */ -class UpstreamConfiguration { - /** - * Creates a new upstream configuration handler - */ - constructor() { - this._upstreams = {}; - this._loadUpstreams(); - } - - _loadUpstreams() { - for (var upstream of config.upstreams) { - var upstreamConfig = upstream; - - if (this._upstreams[upstream.name]) { - LogService.warn("UpstreamConfiguration", "Duplicate upstream " + upstream.name +" - skipping"); - continue; - } - - this._upstreams[upstream.name] = upstreamConfig; - LogService.info("UpstreamConfiguration", "Loaded upstream '" + upstream.name + "' as: " + JSON.stringify(upstreamConfig)); - } - } - - /** - * Checks if a particular upstream exists - * @param {string} name the name of the upstream - * @returns {boolean} true if it is enabled and exists - */ - hasUpstream(name) { - return !!this._upstreams[name]; - } - - /** - * Gets an upstream's configuration - * @param {string} name the upstream name - * @returns {{url:string}} the upstream configuration - */ - getUpstream(name) { - return _.clone(this._upstreams[name]); - } -} - -module.exports = new UpstreamConfiguration(); \ No newline at end of file diff --git a/src/api/ApiError.ts b/src/api/ApiError.ts new file mode 100644 index 0000000..46db457 --- /dev/null +++ b/src/api/ApiError.ts @@ -0,0 +1,39 @@ +/** + * Thrown when there is a problem with a given API call. This is special-cased in the responder + * to create a JSON error response with the status code given. + */ +export class ApiError { + + /** + * The HTTP status code to return + */ + public statusCode: number; + + /** + * An object to be returned as JSON to the caller + */ + public jsonResponse: object; + + /** + * The internal error code to describe what went wrong + */ + public errorCode: string; + + /** + * Creates a new API error + * @param {number} statusCode The HTTP status code to return + * @param {string|object} json An object to be returned as JSON or a message to be returned (which is + * then converted to JSON as {message: "your_message"}) + * @param {string} errCode The internal error code to describe what went wrong + */ + constructor(statusCode: number, json: string | object, errCode = "D_UNKNOWN") { + // Because typescript is just plain dumb + // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript + Error.apply(this, ["ApiError"]); + + if (typeof(json) === "string") json = {message: json}; + this.jsonResponse = json; + this.statusCode = statusCode; + this.errorCode = errCode; + } +} \ No newline at end of file diff --git a/src/api/Webserver.ts b/src/api/Webserver.ts new file mode 100644 index 0000000..669b12b --- /dev/null +++ b/src/api/Webserver.ts @@ -0,0 +1,82 @@ +import * as express from "express"; +import * as path from "path"; +import * as bodyParser from "body-parser"; +import * as URL from "url"; +import { LogService } from "matrix-js-snippets"; +import { Server } from "typescript-rest"; +import * as _ from "lodash"; +import config from "../config"; +import { ApiError } from "./ApiError"; + +/** + * Web server for Dimension. Handles the API routes for the admin, scalar, dimension, and matrix APIs. + */ +export default class Webserver { + + private app: express.Application; + + constructor() { + this.app = express(); + + this.configure(); + this.loadRoutes(); + } + + private loadRoutes() { + const apis = ["scalar", "dimension", "admin", "matrix"].map(a => path.join(__dirname, a, "*.js")); + const router = express.Router(); + apis.forEach(a => Server.loadServices(router, [a])); + const routes = _.uniq(router.stack.map(r => r.route.path)); + for (const route of routes) { + this.app.options(route, (_req, res) => res.sendStatus(200)); + LogService.info("Webserver", "Registered route: " + route); + } + this.app.use(router); + + // 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("*", (_req, res) => { + res.sendFile(path.join(__dirname, "..", "..", "web", "index.html")); + }); + + // Set up the error handler + this.app.use((err: any, _req, res, next) => { + if (err instanceof ApiError) { + // Don't do anything for 'connection reset' + if (res.headersSent) return next(err); + + LogService.warn("Webserver", "Handling ApiError " + err.statusCode + " " + JSON.stringify(err.jsonResponse)); + res.setHeader("Content-Type", "application/json"); + res.status(err.statusCode); + res.json(err.jsonResponse); + } else next(err); + }); + } + + private configure() { + this.app.use(express.static(path.join(__dirname, "..", "..", "web"))); + this.app.use(bodyParser.json()); + this.app.use((req, _res, next) => { + const parsedUrl = URL.parse(req.url, true); + if (parsedUrl.query && parsedUrl.query["scalar_token"]) { + parsedUrl.query["scalar_token"] = "redacted"; + parsedUrl.search = undefined; // to force URL.format to use `query` + } + LogService.info("Webserver", "Incoming request: " + req.method + " " + URL.format(parsedUrl)); + next(); + }); + this.app.use((_req, res, next) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); + }); + } + + /** + * Starts the webserver, bootstrapping the various API handlers + */ + public start() { + 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/AdminAppserviceService.ts b/src/api/admin/AdminAppserviceService.ts new file mode 100644 index 0000000..d3a2dcc --- /dev/null +++ b/src/api/admin/AdminAppserviceService.ts @@ -0,0 +1,88 @@ +import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; +import { AdminService } from "./AdminService"; +import AppService from "../../db/models/AppService"; +import { AppserviceStore } from "../../db/AppserviceStore"; +import { ApiError } from "../ApiError"; +import { MatrixAppserviceClient } from "../../matrix/MatrixAppserviceClient"; +import { LogService } from "matrix-js-snippets"; + +interface AppserviceResponse { + id: string; + hsToken: string; + asToken: string; + userPrefix: string; +} + +interface AppserviceCreateRequest { + userPrefix: string; +} + +/** + * Administrative API for managing the appservices that Dimension operates. + */ +@Path("/api/v1/dimension/admin/appservices") +export class AdminAppserviceService { + + @GET + @Path("all") + public async getAppservices(@QueryParam("scalar_token") scalarToken: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + return (await AppService.findAll()).map(a => this.mapAppservice(a)); + } + + @GET + @Path(":appserviceId") + public async getAppservice(@QueryParam("scalar_token") scalarToken: string, @PathParam("appserviceId") asId: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + try { + const appservice = await AppserviceStore.getAppservice(asId); + return this.mapAppservice(appservice); + } catch (err) { + LogService.error("AdminAppserviceService", err); + throw new ApiError(404, "Appservice not found"); + } + } + + @POST + @Path("new") + public async createAppservice(@QueryParam("scalar_token") scalarToken: string, request: AppserviceCreateRequest): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + // Trim off the @ sign if it's on the prefix + if (request.userPrefix[0] === "@") { + request.userPrefix = request.userPrefix.substring(1); + } + + const appservices = await AppserviceStore.getAllByUserPrefix(request.userPrefix); + if (appservices && appservices.length > 0) { + throw new ApiError(400, "User prefix is already in use"); + } + + const appservice = await AppserviceStore.create(AppserviceStore.getSafeUserId(request.userPrefix)); + LogService.info("AdminAppserviceService", userId + " created an application service"); + return this.mapAppservice(appservice); + } + + @POST + @Path(":appserviceId/test") + public async test(@QueryParam("scalar_token") scalarToken: string, @PathParam("appserviceId") asId: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const appservice = await AppserviceStore.getAppservice(asId); + const client = new MatrixAppserviceClient(appservice); + const userId = await client.whoAmI(); + + if (userId.startsWith("@" + appservice.userPrefix)) return {}; // 200 OK + throw new ApiError(500, "User ID does not match the application service"); + } + + private mapAppservice(as: AppService): AppserviceResponse { + return { + id: as.id, + hsToken: as.hsToken, + asToken: as.asToken, + userPrefix: as.userPrefix, + }; + } +} \ No newline at end of file diff --git a/src/api/admin/AdminIntegrationsService.ts b/src/api/admin/AdminIntegrationsService.ts new file mode 100644 index 0000000..e516ff0 --- /dev/null +++ b/src/api/admin/AdminIntegrationsService.ts @@ -0,0 +1,63 @@ +import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; +import { ApiError } from "../ApiError"; +import { AdminService } from "./AdminService"; +import { DimensionIntegrationsService } from "../dimension/DimensionIntegrationsService"; +import { WidgetStore } from "../../db/WidgetStore"; +import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache"; +import { Integration } from "../../integrations/Integration"; +import { LogService } from "matrix-js-snippets"; +import { BridgeStore } from "../../db/BridgeStore"; + +interface SetEnabledRequest { + enabled: boolean; +} + +interface SetOptionsRequest { + options: any; +} + +/** + * Administrative API for managing the integrations for Dimension. This is to enable/disable integrations + * and set basic options. See the NEB APIs for configuring go-neb instances. + */ +@Path("/api/v1/dimension/admin/integrations") +export class AdminIntegrationsService { + + @POST + @Path(":category/:type/options") + public async setOptions(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string, @PathParam("type") type: string, body: SetOptionsRequest): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + if (category === "widget") await WidgetStore.setOptions(type, body.options); + else throw new ApiError(400, "Unrecognized category"); + + LogService.info("AdminIntegrationsService", userId + " updated the integration options for " + category + "/" + type); + Cache.for(CACHE_INTEGRATIONS).clear(); + return {}; // 200 OK + } + + + @POST + @Path(":category/:type/enabled") + public async setEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string, @PathParam("type") type: string, body: SetEnabledRequest): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + if (category === "widget") await WidgetStore.setEnabled(type, body.enabled); + else if (category === "bridge") await BridgeStore.setEnabled(type, body.enabled); + else throw new ApiError(400, "Unrecognized category"); + + LogService.info("AdminIntegrationsService", userId + " set " + category + "/" + type + " to " + (body.enabled ? "enabled" : "disabled")); + Cache.for(CACHE_INTEGRATIONS).clear(); + return {}; // 200 OK + } + + @GET + @Path(":category/all") + public async getAllIntegrations(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + if (category === "widget") return await DimensionIntegrationsService.getWidgets(false); + else if (category === "bridge") return await DimensionIntegrationsService.getBridges(false, userId); + else throw new ApiError(400, "Unrecongized category"); + } +} \ No newline at end of file diff --git a/src/api/admin/AdminIrcService.ts b/src/api/admin/AdminIrcService.ts new file mode 100644 index 0000000..f5c2310 --- /dev/null +++ b/src/api/admin/AdminIrcService.ts @@ -0,0 +1,132 @@ +import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; +import { AdminService } from "./AdminService"; +import { Cache, CACHE_INTEGRATIONS, CACHE_IRC_BRIDGE } from "../../MemoryCache"; +import { LogService } from "matrix-js-snippets"; +import { ApiError } from "../ApiError"; +import IrcBridgeRecord from "../../db/models/IrcBridgeRecord"; +import { AvailableNetworks, IrcBridge } from "../../bridges/IrcBridge"; +import Upstream from "../../db/models/Upstream"; +import IrcBridgeNetwork from "../../db/models/IrcBridgeNetwork"; + +interface CreateWithUpstream { + upstreamId: number; +} + +interface CreateSelfhosted { + provisionUrl: string; +} + +interface BridgeResponse { + id: number; + upstreamId?: number; + provisionUrl?: string; + isEnabled: boolean; + availableNetworks: AvailableNetworks; +} + +interface SetEnabledRequest { + isEnabled: boolean; +} + +/** + * Administrative API for configuring IRC bridge instances. + */ +@Path("/api/v1/dimension/admin/irc") +export class AdminIrcService { + + @GET + @Path("all") + public async getBridges(@QueryParam("scalar_token") scalarToken: string): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const bridges = await IrcBridgeRecord.findAll(); + const client = new IrcBridge(userId); + return Promise.all(bridges.map(async b => { + return { + id: b.id, + upstreamId: b.upstreamId, + provisionUrl: b.provisionUrl, + isEnabled: b.isEnabled, + availableNetworks: await client.getNetworks(b), + }; + })); + } + + @GET + @Path(":bridgeId") + public async getBridge(@QueryParam("scalar_token") scalarToken: string, @PathParam("bridgeId") bridgeId: number): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const ircBridge = await IrcBridgeRecord.findByPrimary(bridgeId); + if (!ircBridge) throw new ApiError(404, "IRC Bridge not found"); + + const client = new IrcBridge(userId); + return { + id: ircBridge.id, + upstreamId: ircBridge.upstreamId, + provisionUrl: ircBridge.provisionUrl, + isEnabled: ircBridge.isEnabled, + availableNetworks: await client.getNetworks(ircBridge), + }; + } + + @POST + @Path(":bridgeId/network/:networkId/enabled") + public async setNetworkEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("bridgeId") bridgeId: number, @PathParam("networkId") networkId: string, request: SetEnabledRequest): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const ircBridge = await IrcBridgeRecord.findByPrimary(bridgeId); + if (!ircBridge) throw new ApiError(404, "IRC Bridge not found"); + + const localNetworkId = IrcBridge.parseNetworkId(networkId).bridgeNetworkId; + const network = await IrcBridgeNetwork.findOne({ + where: { + bridgeId: ircBridge.id, + bridgeNetworkId: localNetworkId, + }, + }); + if (!network) throw new ApiError(404, "Network not found"); + + network.isEnabled = request.isEnabled; + await network.save(); + + LogService.info("AdminIrcService", userId + " toggled the network '" + localNetworkId + "' on bridge " + ircBridge.id); + Cache.for(CACHE_IRC_BRIDGE).clear(); + return {}; // 200 OK + } + + @POST + @Path("new/upstream") + public async newConfigForUpstream(@QueryParam("scalar_token") scalarToken: string, request: CreateWithUpstream): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const upstream = await Upstream.findByPrimary(request.upstreamId); + if (!upstream) throw new ApiError(400, "Upstream not found"); + + const bridge = await IrcBridgeRecord.create({ + upstreamId: request.upstreamId, + isEnabled: true, + }); + LogService.info("AdminIrcService", userId + " created a new IRC Bridge from upstream " + request.upstreamId); + + Cache.for(CACHE_IRC_BRIDGE).clear(); + Cache.for(CACHE_INTEGRATIONS).clear(); + return this.getBridge(scalarToken, bridge.id); + } + + @POST + @Path("new/selfhosted") + public async newSelfhosted(@QueryParam("scalar_token") scalarToken: string, request: CreateSelfhosted): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const bridge = await IrcBridgeRecord.create({ + provisionUrl: request.provisionUrl, + isEnabled: true, + }); + LogService.info("AdminIrcService", userId + " created a new IRC Bridge with provisioning URL " + request.provisionUrl); + + Cache.for(CACHE_IRC_BRIDGE).clear(); + Cache.for(CACHE_INTEGRATIONS).clear(); + return this.getBridge(scalarToken, bridge.id); + } +} \ No newline at end of file diff --git a/src/api/admin/AdminNebService.ts b/src/api/admin/AdminNebService.ts new file mode 100644 index 0000000..55c795a --- /dev/null +++ b/src/api/admin/AdminNebService.ts @@ -0,0 +1,119 @@ +import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; +import { AdminService } from "./AdminService"; +import { Cache, CACHE_INTEGRATIONS, CACHE_NEB } from "../../MemoryCache"; +import { NebStore } from "../../db/NebStore"; +import { NebConfig } from "../../models/neb"; +import { LogService } from "matrix-js-snippets"; +import { ApiError } from "../ApiError"; + +interface CreateWithUpstream { + upstreamId: number; +} + +interface CreateWithAppservice { + appserviceId: string; + adminUrl: string; +} + +interface SetEnabledRequest { + enabled: boolean; +} + + +/** + * Administrative API for configuring go-neb instances. + */ +@Path("/api/v1/dimension/admin/neb") +export class AdminNebService { + + @GET + @Path("all") + public async getNebConfigs(@QueryParam("scalar_token") scalarToken: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const cachedConfigs = Cache.for(CACHE_NEB).get("configurations"); + if (cachedConfigs) return cachedConfigs; + + const configs = await NebStore.getAllConfigs(); + Cache.for(CACHE_NEB).put("configurations", configs); + return configs; + } + + @GET + @Path(":id/config") + public async getNebConfig(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number): Promise { + const configs = await this.getNebConfigs(scalarToken); // does auth for us + const firstConfig = configs.filter(c => c.id === nebId)[0]; + if (!firstConfig) throw new ApiError(404, "Configuration not found"); + return firstConfig; + } + + @POST + @Path(":id/integration/:type/enabled") + public async setIntegrationEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number, @PathParam("type") integrationType: string, request: SetEnabledRequest): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + await NebStore.setIntegrationEnabled(nebId, integrationType, request.enabled); + LogService.info("AdminNebService", userId + " set the " + integrationType + " on NEB " + nebId + " to " + (request.enabled ? "enabled" : "disabled")); + + Cache.for(CACHE_NEB).clear(); + Cache.for(CACHE_INTEGRATIONS).clear(); + return {}; // 200 OK + } + + @POST + @Path(":id/integration/:type/config") + public async setIntegrationConfig(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number, @PathParam("type") integrationType: string, newConfig: any): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + await NebStore.setIntegrationConfig(nebId, integrationType, newConfig); + LogService.info("AdminNebService", userId + " updated the configuration for " + integrationType + " on NEB " + nebId); + + Cache.for(CACHE_NEB).clear(); + Cache.for(CACHE_INTEGRATIONS).clear(); + return {}; // 200 OK + } + + @GET + @Path(":id/integration/:type/config") + public async getIntegrationConfig(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number, @PathParam("type") integrationType: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + return NebStore.getIntegrationConfig(nebId, integrationType); + } + + @POST + @Path("new/upstream") + public async newConfigForUpstream(@QueryParam("scalar_token") scalarToken: string, request: CreateWithUpstream): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + try { + const neb = await NebStore.createForUpstream(request.upstreamId); + LogService.info("AdminNebService", userId + " created a new NEB instance from upstream " + request.upstreamId); + + Cache.for(CACHE_NEB).clear(); + Cache.for(CACHE_INTEGRATIONS).clear(); + return neb; + } catch (err) { + LogService.error("DimensionNebAdminService", err); + throw new ApiError(500, "Error creating go-neb instance"); + } + } + + @POST + @Path("new/appservice") + public async newConfigForAppservice(@QueryParam("scalar_token") scalarToken: string, request: CreateWithAppservice): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + try { + const neb = await NebStore.createForAppservice(request.appserviceId, request.adminUrl); + LogService.info("AdminNebService", userId + " created a new NEB instance from appservice " + request.appserviceId); + + Cache.for(CACHE_NEB).clear(); + Cache.for(CACHE_INTEGRATIONS).clear(); + return neb; + } catch (err) { + LogService.error("DimensionNebAdminService", err); + throw new ApiError(500, "Error creating go-neb instance"); + } + } +} \ No newline at end of file diff --git a/src/api/admin/AdminService.ts b/src/api/admin/AdminService.ts new file mode 100644 index 0000000..6564273 --- /dev/null +++ b/src/api/admin/AdminService.ts @@ -0,0 +1,84 @@ +import { GET, Path, QueryParam } from "typescript-rest"; +import { ScalarService } from "../scalar/ScalarService"; +import config from "../../config"; +import { ApiError } from "../ApiError"; +import { MatrixLiteClient } from "../../matrix/MatrixLiteClient"; +import { CURRENT_VERSION } from "../../version"; +import { getFederationUrl } from "../../matrix/helpers"; + +interface DimensionVersionResponse { + version: string; +} + +interface DimensionConfigResponse { + admins: string[]; + widgetBlacklist: string[]; + homeserver: { + name: string; + userId: string; + federationUrl: string; + clientServerUrl: string; + }; +} + +/** + * Administrative API for general information about Dimension + */ +@Path("/api/v1/dimension/admin") +export class AdminService { + + /** + * Determines if a given user is an administrator + * @param {string} userId The user ID to validate + * @returns {boolean} True if the user is an administrator + */ + public static isAdmin(userId: string) { + return config.admins.indexOf(userId) >= 0; + } + + /** + * Validates the given scalar token to ensure the owner is an administrator. If the + * given scalar token does not belong to an administrator, an ApiError is raised. + * @param {string} scalarToken The scalar token to validate + * @returns {Promise} Resolves to the owner's user ID if they are an administrator + * @throws {ApiError} Thrown with a status code of 401 if the owner is not an administrator + */ + public static async validateAndGetAdminTokenOwner(scalarToken: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken, true); + if (!AdminService.isAdmin(userId)) + throw new ApiError(401, "You must be an administrator to use this API"); + return userId; + } + + @GET + @Path("check") + public async checkIfAdmin(@QueryParam("scalar_token") scalarToken: string): Promise<{}> { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + return {}; // A 200 OK essentially means "you're an admin". + } + + @GET + @Path("version") + public async getVersion(@QueryParam("scalar_token") scalarToken: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + return {version: CURRENT_VERSION}; + } + + @GET + @Path("config") + public async getConfig(@QueryParam("scalar_token") scalarToken: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const client = new MatrixLiteClient(config.homeserver.accessToken); + return { + admins: config.admins, + widgetBlacklist: config.widgetBlacklist, + homeserver: { + name: config.homeserver.name, + userId: await client.whoAmI(), + federationUrl: await getFederationUrl(config.homeserver.name), + clientServerUrl: config.homeserver.clientServerUrl, + }, + }; + } +} \ No newline at end of file diff --git a/src/api/admin/AdminUpstreamService.ts b/src/api/admin/AdminUpstreamService.ts new file mode 100644 index 0000000..5c9d994 --- /dev/null +++ b/src/api/admin/AdminUpstreamService.ts @@ -0,0 +1,69 @@ +import { GET, Path, POST, QueryParam } from "typescript-rest"; +import { AdminService } from "./AdminService"; +import { Cache, CACHE_UPSTREAM } from "../../MemoryCache"; +import Upstream from "../../db/models/Upstream"; +import { LogService } from "matrix-js-snippets"; + +interface UpstreamRepsonse { + id: number; + name: string; + type: string; + scalarUrl: string; + apiUrl: string; +} + +interface NewUpstreamRequest { + name: string; + type: string; + scalarUrl: string; + apiUrl: string; +} + +/** + * Administrative API for managing the instances upstream of this instance. Particularly + * useful for configuring the Modular upstream. + */ +@Path("/api/v1/dimension/admin/upstreams") +export class AdminUpstreamService { + + @GET + @Path("all") + public async getUpstreams(@QueryParam("scalar_token") scalarToken: string): Promise { + await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const cachedUpstreams = Cache.for(CACHE_UPSTREAM).get("upstreams"); + if (cachedUpstreams) return cachedUpstreams; + + const upstreams = await Upstream.findAll(); + const mapped = upstreams.map(u => this.mapUpstream(u)); + Cache.for(CACHE_UPSTREAM).put("upstreams", mapped); + return mapped; + } + + @POST + @Path("new") + public async createUpstream(@QueryParam("scalar_token") scalarToken: string, request: NewUpstreamRequest): Promise { + const userId = await AdminService.validateAndGetAdminTokenOwner(scalarToken); + + const upstream = await Upstream.create({ + name: request.name, + type: request.type, + scalarUrl: request.scalarUrl, + apiUrl: request.apiUrl, + }); + + LogService.info("AdminUpstreamService", userId + " created a new upstream"); + Cache.for(CACHE_UPSTREAM).clear(); + return this.mapUpstream(upstream); + } + + private mapUpstream(upstream: Upstream): UpstreamRepsonse { + return { + id: upstream.id, + name: upstream.name, + type: upstream.type, + scalarUrl: upstream.scalarUrl, + apiUrl: upstream.apiUrl + }; + } +} \ No newline at end of file diff --git a/src/api/dimension/DimensionHealthService.ts b/src/api/dimension/DimensionHealthService.ts new file mode 100644 index 0000000..7cda207 --- /dev/null +++ b/src/api/dimension/DimensionHealthService.ts @@ -0,0 +1,16 @@ +import { GET, Path } from "typescript-rest"; +import { LogService } from "matrix-js-snippets"; + +/** + * API for the health of Dimension + */ +@Path("/api/v1/dimension/health") +export class DimensionHealthService { + + @GET + @Path("heartbeat") + public async heartbeat(): Promise { + LogService.info("DimensionHealthService", "Heartbeat called"); + return {}; // 200 OK + } +} \ No newline at end of file diff --git a/src/api/dimension/DimensionIntegrationsService.ts b/src/api/dimension/DimensionIntegrationsService.ts new file mode 100644 index 0000000..5eae02b --- /dev/null +++ b/src/api/dimension/DimensionIntegrationsService.ts @@ -0,0 +1,150 @@ +import { DELETE, GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; +import { ScalarService } from "../scalar/ScalarService"; +import { Widget } from "../../integrations/Widget"; +import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache"; +import { Integration } from "../../integrations/Integration"; +import { ApiError } from "../ApiError"; +import { WidgetStore } from "../../db/WidgetStore"; +import { SimpleBot } from "../../integrations/SimpleBot"; +import { NebStore } from "../../db/NebStore"; +import { ComplexBot } from "../../integrations/ComplexBot"; +import { Bridge } from "../../integrations/Bridge"; +import { BridgeStore } from "../../db/BridgeStore"; + +export interface IntegrationsResponse { + widgets: Widget[], + bots: SimpleBot[], + complexBots: ComplexBot[], + bridges: Bridge[], +} + +/** + * API for managing integrations, primarily for a given room + */ +@Path("/api/v1/dimension/integrations") +export class DimensionIntegrationsService { + + /** + * Gets a list of widgets + * @param {boolean} enabledOnly True to only return the enabled widgets + * @returns {Promise} Resolves to the widget list + */ + public static async getWidgets(enabledOnly: boolean): Promise { + const cached = Cache.for(CACHE_INTEGRATIONS).get("widgets"); + if (cached) return cached; + + const widgets = await WidgetStore.listAll(enabledOnly ? true : null); + Cache.for(CACHE_INTEGRATIONS).put("widgets", widgets); + return widgets; + } + + /** + * Gets a list of bridges + * @param {boolean} enabledOnly True to only return the enabled bridges + * @param {string} forUserId The requesting user ID + * @param {string} inRoomId If specified, the room ID to list the bridges in + * @returns {Promise} Resolves to the bridge list + */ + public static async getBridges(enabledOnly: boolean, forUserId: string, inRoomId?: string): Promise { + return BridgeStore.listAll(forUserId, enabledOnly ? true : null, inRoomId); + } + + /** + * Gets a list of simple bots + * @param {string} userId The requesting user ID + * @returns {Promise} Resolves to the simple bot list + */ + public static async getSimpleBots(userId: string): Promise { + const cached = Cache.for(CACHE_INTEGRATIONS).get("simple_bots"); + if (cached) return cached; + + const bots = await NebStore.listSimpleBots(userId); + Cache.for(CACHE_INTEGRATIONS).put("simple_bots", bots); + return bots; + } + + /** + * Gets a list of complex bots + * @param {string} userId The requesting user ID + * @param {string} roomId The room ID to get the complex bots for + * @returns {Promise} Resolves to the complex bot list + */ + public static async getComplexBots(userId: string, roomId: string): Promise { + const cached = Cache.for(CACHE_INTEGRATIONS).get("complex_bots_" + roomId); + if (cached) return cached; + + const bots = await NebStore.listComplexBots(userId, roomId); + Cache.for(CACHE_INTEGRATIONS).put("complex_bots_" + roomId, bots); + return bots; + } + + @GET + @Path("room/:roomId") + public async getIntegrationsInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + return { + widgets: await DimensionIntegrationsService.getWidgets(true), + bots: await DimensionIntegrationsService.getSimpleBots(userId), + complexBots: await DimensionIntegrationsService.getComplexBots(userId, roomId), + bridges: await DimensionIntegrationsService.getBridges(true, userId, roomId), + }; + } + + @GET + @Path("room/:roomId/integrations/:category/:type") + public async getIntegrationInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string, @PathParam("category") category: string, @PathParam("type") integrationType: string): Promise { + const roomConfig = await this.getIntegrationsInRoom(scalarToken, roomId); // does auth for us + + if (category === "widget") return roomConfig.widgets.find(i => i.type === integrationType); + else if (category === "bot") return roomConfig.bots.find(i => i.type === integrationType); + else if (category === "complex-bot") return roomConfig.complexBots.find(i => i.type === integrationType); + else if (category === "bridge") return roomConfig.bridges.find(i => i.type === integrationType); + else throw new ApiError(400, "Unrecognized category"); + } + + @POST + @Path("room/:roomId/integrations/:category/:type/config") + public async setIntegrationConfigurationInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string, @PathParam("category") category: string, @PathParam("type") integrationType: string, newConfig: any): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + + if (category === "complex-bot") await NebStore.setComplexBotConfig(userId, integrationType, roomId, newConfig); + else if (category === "bridge") await BridgeStore.setBridgeRoomConfig(userId, integrationType, roomId, newConfig); + else throw new ApiError(400, "Unrecognized category"); + + Cache.for(CACHE_INTEGRATIONS).clear(); // TODO: Improve which cache we invalidate + return {}; // 200 OK + } + + @DELETE + @Path("room/:roomId/integrations/:category/:type") + public async removeIntegrationInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string, @PathParam("category") category: string, @PathParam("type") integrationType: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + + if (category === "widget") throw new ApiError(400, "Widgets should be removed client-side"); + else if (category === "bot") await NebStore.removeSimpleBot(integrationType, roomId, userId); + else if (category === "complex-bot") throw new ApiError(400, "Complex bots should be removed automatically"); + else if (category === "bridge") throw new ApiError(400, "Bridges should be removed automatically"); + else throw new ApiError(400, "Unrecognized category"); + + Cache.for(CACHE_INTEGRATIONS).clear(); // TODO: Improve which cache we invalidate + return {}; // 200 OK + } + + @GET + @Path(":category/:type") + public async getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise { + // This is intentionally an unauthed endpoint to ensure we can use it in widgets + + let integrations: Integration[] = []; + if (category === "widget") integrations = await DimensionIntegrationsService.getWidgets(true); + else throw new ApiError(400, "Unsupported category"); + + for (const integration of integrations) { + if (integration.category === category && integration.type === type) { + return integration; + } + } + + throw new ApiError(404, "Integration not found"); + } +} \ No newline at end of file diff --git a/src/api/dimension/DimensionIrcService.ts b/src/api/dimension/DimensionIrcService.ts new file mode 100644 index 0000000..827d94b --- /dev/null +++ b/src/api/dimension/DimensionIrcService.ts @@ -0,0 +1,65 @@ +import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; +import { LogService } from "matrix-js-snippets"; +import { ScalarService } from "../scalar/ScalarService"; +import { IrcBridge } from "../../bridges/IrcBridge"; +import IrcBridgeRecord from "../../db/models/IrcBridgeRecord"; +import { ApiError } from "../ApiError"; + +interface RequestLinkRequest { + op: string; +} + +/** + * API for interacting with the IRC bridge + */ +@Path("/api/v1/dimension/irc") +export class DimensionIrcService { + + @GET + @Path(":networkId/channel/:channel/ops") + public async getOps(@QueryParam("scalar_token") scalarToken: string, @PathParam("networkId") networkId: string, @PathParam("channel") channelNoHash: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + + const parsed = IrcBridge.parseNetworkId(networkId); + const bridge = await IrcBridgeRecord.findByPrimary(parsed.bridgeId); + if (!bridge) throw new ApiError(404, "Bridge not found"); + + const client = new IrcBridge(userId); + const operators = await client.getOperators(bridge, parsed.bridgeNetworkId, "#" + channelNoHash); + + LogService.info("DimensionIrcService", userId + " listed the operators for #" + channelNoHash + " on " + networkId); + return operators; + } + + @POST + @Path(":networkId/channel/:channel/link/:roomId") + public async requestLink(@QueryParam("scalar_token") scalarToken: string, @PathParam("networkId") networkId: string, @PathParam("channel") channelNoHash: string, @PathParam("roomId") roomId: string, request: RequestLinkRequest): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + + const parsed = IrcBridge.parseNetworkId(networkId); + const bridge = await IrcBridgeRecord.findByPrimary(parsed.bridgeId); + if (!bridge) throw new ApiError(404, "Bridge not found"); + + const client = new IrcBridge(userId); + await client.requestLink(bridge, parsed.bridgeNetworkId, "#" + channelNoHash, request.op, roomId); + + LogService.info("DimensionIrcService", userId + " requested #" + channelNoHash + " on " + networkId + " to be linked to " + roomId); + return {}; // 200 OK + } + + @POST + @Path(":networkId/channel/:channel/unlink/:roomId") + public async unlink(@QueryParam("scalar_token") scalarToken: string, @PathParam("networkId") networkId: string, @PathParam("channel") channelNoHash: string, @PathParam("roomId") roomId: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + + const parsed = IrcBridge.parseNetworkId(networkId); + const bridge = await IrcBridgeRecord.findByPrimary(parsed.bridgeId); + if (!bridge) throw new ApiError(404, "Bridge not found"); + + const client = new IrcBridge(userId); + await client.removeLink(bridge, parsed.bridgeNetworkId, "#" + channelNoHash, roomId); + + LogService.info("DimensionIrcService", userId + " unlinked #" + channelNoHash + " on " + networkId + " from " + roomId); + return {}; // 200 OK + } +} \ No newline at end of file diff --git a/src/api/dimension/DimensionWebhookService.ts b/src/api/dimension/DimensionWebhookService.ts new file mode 100644 index 0000000..cfc8171 --- /dev/null +++ b/src/api/dimension/DimensionWebhookService.ts @@ -0,0 +1,38 @@ +import { FormParam, HeaderParam, Path, PathParam, POST } from "typescript-rest"; +import Webhook from "../../db/models/Webhook"; +import { ApiError } from "../ApiError"; +import * as request from "request"; +import { LogService } from "matrix-js-snippets"; + +/** + * API for proxying webhooks to other services. + */ +@Path("/api/v1/dimension/webhooks") +export class DimensionWebhookService { + + @POST + @Path("/travisci/:webhookId") + public async postTravisCiWebhook(@PathParam("webhookId") webhookId: string, @FormParam("payload") payload: string, @HeaderParam("Signature") signature: string): Promise { + const webhook = await Webhook.findByPrimary(webhookId).catch(() => null); + if (!webhook) throw new ApiError(404, "Webhook not found"); + if (!webhook.targetUrl) throw new ApiError(400, "Webhook not configured"); + + return new Promise((resolve, _reject) => { + request({ + method: "POST", + url: webhook.targetUrl, + form: {payload: payload}, + headers: { + "Signature": signature, + }, + }, (err, res, _body) => { + if (err) { + LogService.error("DimensionWebhooksService", "Error invoking travis-ci webhook"); + LogService.error("DimensionWebhooksService", res.body); + + throw new ApiError(500, "Internal Server Error"); + } else resolve(); + }); + }); + } +} \ No newline at end of file diff --git a/src/api/dimension/DimensionWidgetService.ts b/src/api/dimension/DimensionWidgetService.ts new file mode 100644 index 0000000..2876dd2 --- /dev/null +++ b/src/api/dimension/DimensionWidgetService.ts @@ -0,0 +1,75 @@ +import { GET, Path, QueryParam } from "typescript-rest"; +import { LogService } from "matrix-js-snippets"; +import * as url from "url"; +import { ApiError } from "../ApiError"; +import * as dns from "dns-then"; +import config from "../../config"; +import { Netmask } from "netmask"; +import * as request from "request"; + +interface EmbedCapabilityResponse { + canEmbed: boolean; +} + +/** + * API for widgets + */ +@Path("/api/v1/dimension/widgets") +export class DimensionWidgetService { + + @GET + @Path("embeddable") + public async isEmbeddable(@QueryParam("url") checkUrl: string): Promise { + LogService.info("DimensionWidgetService", "Checking to see if a url is embeddable: " + checkUrl); + + const parsed = url.parse(checkUrl); + + // Only allow http and https + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { + throw new ApiError(400, "Invalid scheme: " + parsed.protocol); + } + + // Get the IP address we're trying to connect to so we can ensure it's not blacklisted + const hostname = parsed.hostname.split(":")[0]; + let addresses = []; + try { + addresses = await dns.resolve(hostname); + } catch (err) { + LogService.error("DimensionWidgetService", err); + } + if (!addresses || addresses.length === 0) throw new ApiError(400, "Cannot resolve host " + hostname); + + // Check the blacklist + for (const ipOrCidr of config.widgetBlacklist) { + const block = new Netmask(ipOrCidr); + for (const address of addresses) { + if (block.contains(address)) { + throw new ApiError(400, "Address blacklisted"); + } + } + } + + // Now we need to verify we can actually make the request + await new Promise((resolve, _reject) => { + request(checkUrl, (err, response) => { + if (err) { + LogService.error("DimensionWidgetService", err); + throw new ApiError(400, "Failed to contact host"); + } + + if (response.statusCode >= 200 && response.statusCode < 300) { + // 200 OK + const xFrameOptions = (response.headers["x-frame-options"] || '').toLowerCase(); + + if (xFrameOptions === "sameorigin" || xFrameOptions === "deny") { + throw new ApiError(400, "X-Frame-Options prevents embedding"); + } + + resolve(); + } else throw new ApiError(400, "Non-success status code returned"); + }); + }); + + return {canEmbed: true}; + } +} \ No newline at end of file diff --git a/src/api/matrix/MatrixAppServiceApiService.ts b/src/api/matrix/MatrixAppServiceApiService.ts new file mode 100644 index 0000000..b232fae --- /dev/null +++ b/src/api/matrix/MatrixAppServiceApiService.ts @@ -0,0 +1,77 @@ +import { GET, Path, PathParam, PUT, QueryParam } from "typescript-rest"; +import { ApiError } from "../ApiError"; +import { LogService } from "matrix-js-snippets"; +import { SimplifiedMatrixEvent } from "../../models/MatrixEvent"; +import { AppserviceStore } from "../../db/AppserviceStore"; + +interface AppServiceTransaction { + events: SimplifiedMatrixEvent[]; +} + +/** + * API for handling appservice traffic from a homeserver + */ +// Note: There's no actual defined prefix for this API. The following was chosen to be +// somewhat consistent with the other matrix APIs. In reality, the homeserver will just +// hit the URL given in the registration - be sure to define it to match this prefix. +// Eg: `url: "http://localhost:8184/_matrix/appservice/r0"` +@Path("/_matrix/appservice/r0") +export class MatrixAppServiceApiService { + + @PUT + @Path("/transactions/:txnId") + public async onTransaction(@QueryParam("access_token") homeserverToken: string, @PathParam("txnId") txnId: string, _txn: AppServiceTransaction): Promise { + try { + const appservice = await AppserviceStore.getByHomeserverToken(homeserverToken); + + // We don't handle the transaction at all - we just don't want the homeserver to consider us down + LogService.verbose("MatrixAppServiceApiService", "Accepting transaction " + txnId + " for appservice " + appservice.id + " blindly"); + return {}; // 200 OK + } catch (err) { + LogService.error("MatrixAppServiceApiService", err); + throw new ApiError(403, {errcode: "M_FORBIDDEN"}); + } + } + + @GET + @Path("/room/:alias") + public async getRoom(@QueryParam("access_token") homeserverToken: string, @PathParam("alias") roomAlias: string): Promise { + try { + const appservice = await AppserviceStore.getByHomeserverToken(homeserverToken); + + // We don't support room lookups + LogService.verbose("MatrixAppServiceApiService", "404ing request for room " + roomAlias + " at appservice " + appservice.id); + throw new ApiError(404, {errcode: "IO.T2BOT.DIMENSION.ROOMS_NOT_SUPPORTED"}); + } catch (err) { + if (err instanceof ApiError) throw err; + LogService.error("MatrixAppServiceApiService", err); + throw new ApiError(403, {errcode: "M_FORBIDDEN"}); + } + } + + @GET + @Path("/user/:userId") + public async getUser(@QueryParam("access_token") homeserverToken: string, @PathParam("userId") userId: string): Promise { + try { + const appservice = await AppserviceStore.getByHomeserverToken(homeserverToken); + + try { + const user = await AppserviceStore.getUser(appservice.id, userId); + return { + userId: user.id, + displayName: user.displayName, + avatarUrl: user.avatarUrl, + } + } catch (err) { + LogService.error("MatrixAppServiceApiService", err); + throw new ApiError(404, {errcode: "IO.T2BOT.DIMENSION.USER_NOT_FOUND"}); + } + } catch (err) { + if (err instanceof ApiError) throw err; + + LogService.error("MatrixAppServiceApiService", err); + throw new ApiError(403, {errcode: "M_FORBIDDEN"}); + } + } + +} \ No newline at end of file diff --git a/src/api/scalar/ScalarService.ts b/src/api/scalar/ScalarService.ts new file mode 100644 index 0000000..2bfc524 --- /dev/null +++ b/src/api/scalar/ScalarService.ts @@ -0,0 +1,98 @@ +import { GET, Path, POST, QueryParam } from "typescript-rest"; +import { MatrixOpenIdClient } from "../../matrix/MatrixOpenIdClient"; +import Upstream from "../../db/models/Upstream"; +import { ScalarClient } from "../../scalar/ScalarClient"; +import User from "../../db/models/User"; +import UserScalarToken from "../../db/models/UserScalarToken"; +import { LogService } from "matrix-js-snippets"; +import { ApiError } from "../ApiError"; +import * as randomString from "random-string"; +import { OpenId } from "../../models/OpenId"; +import { ScalarAccountResponse, ScalarRegisterResponse } from "../../models/ScalarResponses"; +import { Cache, CACHE_SCALAR_ACCOUNTS } from "../../MemoryCache"; +import { ScalarStore } from "../../db/ScalarStore"; + +interface RegisterRequest { + access_token: string; + token_type: string; + matrix_server_name: string; + expires_in: number; +} + +/** + * API for the minimum Scalar API we need to implement to be compatible with clients. Used for registration + * and general account management. + */ +@Path("/api/v1/scalar") +export class ScalarService { + + /** + * Gets the owner of a given scalar token, throwing an ApiError if the token is invalid. + * @param {string} scalarToken The scalar token to validate + * @param {boolean} ignoreUpstreams True to consider the token valid if it is missing links to other upstreams + * @returns {Promise} Resolves to the owner's user ID if the token is valid. + * @throws {ApiError} Thrown with a status code of 401 if the token is invalid. + */ + public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise { + const cachedUserId = Cache.for(CACHE_SCALAR_ACCOUNTS).get(scalarToken); + if (cachedUserId) return cachedUserId; + + try { + const user = await ScalarStore.getTokenOwner(scalarToken, ignoreUpstreams); + Cache.for(CACHE_SCALAR_ACCOUNTS).put(scalarToken, user.userId, 30 * 60 * 1000); // 30 minutes + return user.userId; + } catch (err) { + LogService.error("ScalarService", err); + throw new ApiError(401, "Invalid token"); + } + } + + @POST + @Path("register") + public async register(request: RegisterRequest): Promise { + const mxClient = new MatrixOpenIdClient(request); + const mxUserId = await mxClient.getUserId(); + + const user = await User.findByPrimary(mxUserId); + if (!user) { + // There's a small chance we'll get a validation error because of: + // https://github.com/vector-im/riot-web/issues/5846 + LogService.verbose("ScalarService", "User " + mxUserId + " never seen before - creating"); + await User.create({userId: mxUserId}); + } + + const upstreams = await Upstream.findAll(); + await Promise.all(upstreams.map(async upstream => { + const tokens = await UserScalarToken.findAll({where: {userId: mxUserId, upstreamId: upstream.id}}); + if (!tokens || tokens.length === 0) { + LogService.info("ScalarService", "Registering " + mxUserId + " for a token at upstream " + upstream.id + " (" + upstream.name + ")"); + const client = new ScalarClient(upstream); + const response = await client.register(request); + return UserScalarToken.create({ + userId: mxUserId, + scalarToken: response.scalar_token, + isDimensionToken: false, + upstreamId: upstream.id, + }); + } + })); + + const dimensionToken = randomString({length: 25}); + const dimensionScalarToken = await UserScalarToken.create({ + userId: mxUserId, + scalarToken: dimensionToken, + isDimensionToken: true, + }); + + LogService.info("ScalarService", mxUserId + " has registered for a scalar token successfully"); + return {scalar_token: dimensionScalarToken.scalarToken}; + } + + @GET + @Path("account") + public async getAccount(@QueryParam("scalar_token") scalarToken: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + return {user_id: userId}; + } + +} \ No newline at end of file diff --git a/src/api/scalar/ScalarWidgetService.ts b/src/api/scalar/ScalarWidgetService.ts new file mode 100644 index 0000000..7de17e9 --- /dev/null +++ b/src/api/scalar/ScalarWidgetService.ts @@ -0,0 +1,72 @@ +import { GET, Path, QueryParam } from "typescript-rest"; +import { LogService } from "matrix-js-snippets"; +import { Cache, CACHE_WIDGET_TITLES } from "../../MemoryCache"; +import { MatrixLiteClient } from "../../matrix/MatrixLiteClient"; +import config from "../../config"; +import { ScalarService } from "./ScalarService"; +import moment = require("moment"); + +interface UrlPreviewResponse { + cached_response: boolean; + page_title_cache_item: { + expires: string; // "2017-12-18T04:20:04.001806738Z" + cached_response_err: string; + cached_title: string; // the actual thing riot uses + }; + error: { + message: string; + }; +} + +/** + * API for the minimum Scalar API for widget functionality in clients. + */ +@Path("/api/v1/scalar/widgets") +export class ScalarWidgetService { + + @GET + @Path("title_lookup") + public async titleLookup(@QueryParam("scalar_token") scalarToken: string, @QueryParam("curl") url: string): Promise { + await ScalarService.getTokenOwner(scalarToken); + + const cachedResult = Cache.for(CACHE_WIDGET_TITLES).get(url); + if (cachedResult) { + cachedResult.cached_response = true; + return cachedResult; + } + + const client = new MatrixLiteClient(config.homeserver.accessToken); + + try { + const preview = await client.getUrlPreview(url); + const expirationTime = 60 * 80 * 1000; // 1 hour + const expirationAsString = moment().add(expirationTime, "milliseconds").toISOString(); + const cachedItem = { + cached_response: false, // we're not cached yet + page_title_cache_item: { + expires: expirationAsString, + cached_response_err: null, + cached_title: preview["og:title"], + }, + error: {message: null}, + }; + Cache.for(CACHE_WIDGET_TITLES).put(url, cachedItem, expirationTime); + return cachedItem; + } catch (err) { + LogService.error("ScalarWidgetService", "Error getting URL preview"); + LogService.error("ScalarWidgetService", err); + return { + // All of this is to match scalar's response :/ + cached_response: false, + page_title_cache_item: { + expires: null, + cached_response_err: "Failed to get URL preview", + cached_title: null, + }, + error: { + message: "Failed to get URL preview", + }, + }; + } + } +} \ No newline at end of file diff --git a/src/bridges/IrcBridge.ts b/src/bridges/IrcBridge.ts new file mode 100644 index 0000000..1b45d44 --- /dev/null +++ b/src/bridges/IrcBridge.ts @@ -0,0 +1,327 @@ +import { IrcBridgeConfiguration } from "../integrations/Bridge"; +import { Cache, CACHE_IRC_BRIDGE } from "../MemoryCache"; +import IrcBridgeRecord from "../db/models/IrcBridgeRecord"; +import Upstream from "../db/models/Upstream"; +import UserScalarToken from "../db/models/UserScalarToken"; +import { LogService } from "matrix-js-snippets"; +import * as request from "request"; +import { ListLinksResponseItem, ListOpsResponse, QueryNetworksResponse } from "./models/irc"; +import IrcBridgeNetwork from "../db/models/IrcBridgeNetwork"; +import { ModularIrcResponse } from "../models/ModularResponses"; + +interface CachedNetwork { + ircBridgeId: number; + bridgeNetworkId: string; + bridgeUserId: string; + displayName: string; + domain: string; + isEnabled: boolean; +} + +export interface AvailableNetworks { + [networkId: string]: { + name: string; + domain: string; + bridgeUserId: string; + isEnabled: boolean; + }; +} + +export interface LinkedChannels { + [networkId: string]: { + channelName: string; + }[]; +} + +export class IrcBridge { + private static getNetworkId(network: CachedNetwork): string { + return network.ircBridgeId + "-" + network.bridgeNetworkId; + } + + public static parseNetworkId(networkId: string): { bridgeId: string, bridgeNetworkId: string } { + const parts = networkId.split("-"); + const bridgeId = parts.splice(0, 1)[0]; + const bridgeNetworkId = parts.join("-"); + return {bridgeId, bridgeNetworkId}; + } + + constructor(private requestingUserId: string) { + } + + public async hasNetworks(): Promise { + const allNetworks = (await this.getAllNetworks()).filter(n => n.isEnabled); + return allNetworks.length > 0; + } + + public async getNetworks(bridge?: IrcBridgeRecord, enabledOnly?: boolean): Promise { + let networks = await this.getAllNetworks(); + if (bridge) networks = networks.filter(n => n.ircBridgeId === bridge.id); + if (enabledOnly) networks = networks.filter(n => n.isEnabled); + + const available: AvailableNetworks = {}; + networks.forEach(n => available[IrcBridge.getNetworkId(n)] = { + name: n.displayName, + domain: n.domain, + bridgeUserId: n.bridgeUserId, + isEnabled: n.isEnabled, + }); + return available; + } + + public async getRoomConfiguration(inRoomId: string): Promise { + const availableNetworks = await this.getNetworks(null, true); + const bridges = await IrcBridgeRecord.findAll({where: {isEnabled: true}}); + const linkedChannels: LinkedChannels = {}; + + for (const bridge of bridges) { + const links = await this.fetchLinks(bridge, inRoomId); + for (const key of Object.keys(links)) { + linkedChannels[key] = links[key]; + } + } + + return {availableNetworks: availableNetworks, links: linkedChannels}; + } + + public async getOperators(bridge: IrcBridgeRecord, networkId: string, channel: string): Promise { + const network = (await this.getAllNetworks()).find(n => n.isEnabled && n.ircBridgeId === bridge.id && n.bridgeNetworkId === networkId); + if (!network) throw new Error("Network not found"); + + const requestBody = {remote_room_server: network.domain, remote_room_channel: channel}; + + let responses: ListOpsResponse[] = []; + if (bridge.upstreamId) { + const result = await this.doUpstreamRequest>(bridge, "POST", "/bridges/irc/_matrix/provision/querylink", null, requestBody); + if (result && result.replies) responses = result.replies.map(r => r.response); + } else { + const result = await this.doProvisionRequest(bridge, "POST", "/_matrix/provision/querylink", null, requestBody); + if (result) responses = [result]; + } + + const ops: string[] = []; + for (const response of responses) { + if (!response || !response.operators) continue; + response.operators.forEach(i => ops.push(i)); + } + + return ops; + } + + public async requestLink(bridge: IrcBridgeRecord, networkId: string, channel: string, op: string, inRoomId: string): Promise { + const network = (await this.getAllNetworks()).find(n => n.isEnabled && n.ircBridgeId === bridge.id && n.bridgeNetworkId === networkId); + if (!network) throw new Error("Network not found"); + + const requestBody = { + remote_room_server: network.domain, + remote_room_channel: channel, + matrix_room_id: inRoomId, + op_nick: op, + user_id: this.requestingUserId, + }; + + if (bridge.upstreamId) { + delete requestBody["user_id"]; + await this.doUpstreamRequest(bridge, "POST", "/bridges/irc/_matrix/provision/link", null, requestBody); + } else { + await this.doProvisionRequest(bridge, "POST", "/_matrix/provision/link", null, requestBody); + } + } + + public async removeLink(bridge: IrcBridgeRecord, networkId: string, channel: string, inRoomId: string): Promise { + const network = (await this.getAllNetworks()).find(n => n.isEnabled && n.ircBridgeId === bridge.id && n.bridgeNetworkId === networkId); + if (!network) throw new Error("Network not found"); + + const requestBody = { + remote_room_server: network.domain, + remote_room_channel: channel, + matrix_room_id: inRoomId, + user_id: this.requestingUserId, + }; + + if (bridge.upstreamId) { + delete requestBody["user_id"]; + await this.doUpstreamRequest(bridge, "POST", "/bridges/irc/_matrix/provision/unlink", null, requestBody); + } else { + await this.doProvisionRequest(bridge, "POST", "/_matrix/provision/unlink", null, requestBody); + } + } + + private async getAllNetworks(): Promise { + const cached = Cache.for(CACHE_IRC_BRIDGE).get("networks"); + if (cached) return cached; + + const bridges = await IrcBridgeRecord.findAll(); + if (!bridges) return []; + + const networks: CachedNetwork[] = []; + for (const bridge of bridges) { + const bridgeNetworks = await this.fetchNetworks(bridge); + bridgeNetworks.forEach(n => networks.push(n)); + } + + Cache.for(CACHE_IRC_BRIDGE).put("networks", networks, 60 * 60 * 1000); // 1 hour + return networks; + } + + private async fetchLinks(bridge: IrcBridgeRecord, inRoomId: string): Promise { + const availableNetworks = await this.getNetworks(bridge, true); + const networksByDomain: { [domain: string]: { id: string, name: string, bridgeUserId: string } } = {}; + for (const key of Object.keys(availableNetworks)) { + const network = availableNetworks[key]; + networksByDomain[network.domain] = { + id: key, + name: network.name, + bridgeUserId: network.bridgeUserId, + }; + } + + let responses: ListLinksResponseItem[] = []; + if (bridge.upstreamId) { + const result = await this.doUpstreamRequest>(bridge, "GET", "/bridges/irc/_matrix/provision/listlinks/" + inRoomId); + if (result && result.replies) { + const replies = result.replies.map(r => r.response); + for (const reply of replies) reply.forEach(r => responses.push(r)); + } + } else { + const result = await this.doProvisionRequest(bridge, "GET", "/_matrix/provision/listlinks/" + inRoomId); + if (result) responses = result; + } + + const linked: LinkedChannels = {}; + for (const response of responses) { + if (!response || !response.remote_room_server) continue; + + const network = networksByDomain[response.remote_room_server]; + if (!network) continue; + + if (!linked[network.id]) linked[network.id] = []; + linked[network.id].push({ + channelName: response.remote_room_channel, + }); + } + + return linked; + } + + private async fetchNetworks(bridge: IrcBridgeRecord): Promise { + let responses: QueryNetworksResponse[] = []; + if (bridge.upstreamId) { + const result = await this.doUpstreamRequest>(bridge, "GET", "/bridges/irc/_matrix/provision/querynetworks"); + if (result && result.replies) responses = result.replies.map(r => r.response); + } else { + const result = await this.doProvisionRequest(bridge, "GET", "/_matrix/provision/querynetworks"); + if (result) responses = [result]; + } + + const networks: CachedNetwork[] = []; + for (const response of responses) { + if (!response || !response.servers) continue; + + for (const server of response.servers) { + if (!server) continue; + + let existingNetwork = await IrcBridgeNetwork.findOne({ + where: { + bridgeId: bridge.id, + bridgeNetworkId: server.network_id, + }, + }); + if (!existingNetwork) { + LogService.info("IrcBridge", "Discovered new network for bridge " + bridge.id + ": " + server.network_id); + existingNetwork = await IrcBridgeNetwork.create({ + bridgeId: bridge.id, + isEnabled: false, + bridgeNetworkId: server.network_id, + bridgeUserId: server.bot_user_id, + displayName: server.desc, + domain: server.fields.domain, + }); + } else { + existingNetwork.displayName = server.desc; + existingNetwork.bridgeUserId = server.bot_user_id; + existingNetwork.domain = server.fields.domain; + await existingNetwork.save(); + } + + networks.push({ + ircBridgeId: bridge.id, + bridgeNetworkId: existingNetwork.bridgeNetworkId, + bridgeUserId: existingNetwork.bridgeUserId, + displayName: existingNetwork.displayName, + domain: existingNetwork.domain, + isEnabled: existingNetwork.isEnabled, + }); + } + } + + return networks; + } + + private async doUpstreamRequest(bridge: IrcBridgeRecord, method: string, endpoint: string, qs?: any, body?: any): Promise { + const upstream = await Upstream.findByPrimary(bridge.upstreamId); + const token = await UserScalarToken.findOne({ + where: { + upstreamId: upstream.id, + isDimensionToken: false, + userId: this.requestingUserId, + }, + }); + + if (!qs) qs = {}; + qs["scalar_token"] = token.scalarToken; + + const apiUrl = upstream.apiUrl.endsWith("/") ? upstream.apiUrl.substring(0, upstream.apiUrl.length - 1) : upstream.apiUrl; + const url = apiUrl + (endpoint.startsWith("/") ? endpoint : "/" + endpoint); + LogService.info("IrcBridge", "Doing upstream IRC Bridge request: " + url); + + return new Promise((resolve, reject) => { + request({ + method: method, + url: url, + qs: qs, + json: body, + }, (err, res, _body) => { + if (err) { + LogService.error("IrcBridge", "Error calling" + url); + LogService.error("IrcBridge", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("IrcBridge", "Got status code " + res.statusCode + " when calling " + url); + LogService.error("IrcBridge", res.body); + reject(new Error("Request failed")); + } else { + if (typeof(res.body) === "string") res.body = JSON.parse(res.body); + resolve(res.body); + } + }); + }); + } + + private async doProvisionRequest(bridge: IrcBridgeRecord, method: string, endpoint: string, qs?: any, body?: any): Promise { + const provisionUrl = bridge.provisionUrl; + const apiUrl = provisionUrl.endsWith("/") ? provisionUrl.substring(0, provisionUrl.length - 1) : provisionUrl; + const url = apiUrl + (endpoint.startsWith("/") ? endpoint : "/" + endpoint); + LogService.info("IrcBridge", "Doing provision IRC Bridge request: " + url); + + return new Promise((resolve, reject) => { + request({ + method: method, + url: url, + qs: qs, + json: body, + }, (err, res, _body) => { + if (err) { + LogService.error("IrcBridge", "Error calling" + url); + LogService.error("IrcBridge", err); + reject(err); + LogService.error("IrcBridge", "Got status code " + res.statusCode + " when calling " + url); + LogService.error("IrcBridge", res.body); + reject(new Error("Request failed")); + } else { + if (typeof(res.body) === "string") res.body = JSON.parse(res.body); + resolve(res.body); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/bridges/models/irc.ts b/src/bridges/models/irc.ts new file mode 100644 index 0000000..b8dde4b --- /dev/null +++ b/src/bridges/models/irc.ts @@ -0,0 +1,20 @@ +export interface QueryNetworksResponse { + servers: { + network_id: string; + bot_user_id: string; + desc: string; + fields: { + domain: string; + }; + }[]; +} + +export interface ListLinksResponseItem { + matrix_room_id: string; + remote_room_channel: string; + remote_room_server: string; +} + +export interface ListOpsResponse { + operators: string[]; +} \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..cca6074 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,23 @@ +import * as config from "config"; +import { LogConfig } from "matrix-js-snippets"; + +export interface DimensionConfig { + web: { + port: number; + address: string; + }; + homeserver: { + name: string; + accessToken: string; + clientServerUrl: string; + federationUrl: string; + }; + widgetBlacklist: string[]; + database: { + file: string; + }; + admins: string[]; + logging: LogConfig; +} + +export default config; \ No newline at end of file diff --git a/src/db/AppserviceStore.ts b/src/db/AppserviceStore.ts new file mode 100644 index 0000000..2a16d60 --- /dev/null +++ b/src/db/AppserviceStore.ts @@ -0,0 +1,80 @@ +import AppService from "./models/AppService"; +import AppServiceUser from "./models/AppServiceUser"; +import * as randomString from "random-string"; +import { MatrixAppserviceClient } from "../matrix/MatrixAppserviceClient"; +import { LogService } from "matrix-js-snippets"; + +export class AppserviceStore { + + public static async create(userPrefix: string): Promise { + const id = "dimension-" + randomString({length: 25}); + const asToken = randomString({length: 100}); + const hsToken = randomString({length: 100}); + + return AppService.create({ + id: id, + asToken: asToken, + hsToken: hsToken, + userPrefix: userPrefix, + }); + } + + public static async getUser(appserviceId: string, userId: string): Promise { + const user = await AppServiceUser.findOne({where: {appserviceId: appserviceId, id: userId}}); + if (!user) throw new Error("User not found"); + return user; + } + + public static async getByHomeserverToken(hsToken: string): Promise { + const appservice = await AppService.findOne({where: {hsToken: hsToken}}); + if (!appservice) throw new Error("Appservice not found"); + return appservice; + } + + public static async getAllByUserPrefix(userPrefix: string): Promise { + return AppService.findAll({where: {userPrefix: userPrefix}}); + } + + public static getSafeUserId(userIdOrPrefix: string): string { + // Force user IDs to be lowercase and strip out characters that aren't permitted + // https://matrix.org/docs/spec/appendices.html#user-identifiers + if (userIdOrPrefix.startsWith('@')) { + // we only change out the parts we care about. The rest will be crushed down. + userIdOrPrefix = userIdOrPrefix.replace(/@/g, '=40'); + userIdOrPrefix = userIdOrPrefix.replace(/:/g, '=3A'); + } + return userIdOrPrefix.toLowerCase().replace(/[^a-z0-9._\-=]/g, '.'); + } + + public static async registerUser(appserviceId: string, userId: string): Promise { + const appservice = await AppService.findOne({where: {id: appserviceId}}); + if (!appservice) throw new Error("Appservice not found"); + + LogService.info("AppserviceStore", "Registering to own " + userId + " in appservice " + appserviceId); + const client = new MatrixAppserviceClient(appservice); + const localpart = AppserviceStore.getSafeUserId(userId.substring(1).split(":")[0]); + const response = await client.registerUser(localpart); + LogService.info("AppserviceStore", "Successfully registered " + userId); + + return await AppServiceUser.create({ + id: userId, + appserviceId: appserviceId, + accessToken: response.access_token, + }); + } + + public static async getOrCreateUser(appserviceId: string, userId: string): Promise { + const user = await AppServiceUser.findOne({where: {appserviceId: appserviceId, id: userId}}); + if (!user) return AppserviceStore.registerUser(appserviceId, userId); + return user; + } + + public static async getAppservice(id: string): Promise { + const appservice = await AppService.findByPrimary(id); + if (!appservice) throw new Error("Appservice not found"); + return appservice; + } + + private constructor() { + } +} \ No newline at end of file diff --git a/src/db/BridgeStore.ts b/src/db/BridgeStore.ts new file mode 100644 index 0000000..5c502b9 --- /dev/null +++ b/src/db/BridgeStore.ts @@ -0,0 +1,62 @@ +import { Bridge } from "../integrations/Bridge"; +import BridgeRecord from "./models/BridgeRecord"; +import { IrcBridge } from "../bridges/IrcBridge"; + +export class BridgeStore { + + public static async listAll(requestingUserId: string, isEnabled?: boolean, inRoomId?: string): Promise { + let conditions = {}; + if (isEnabled === true || isEnabled === false) conditions = {where: {isEnabled: isEnabled}}; + + const allRecords = await BridgeRecord.findAll(conditions); + const enabledBridges: Bridge[] = []; + + for (const bridgeRecord of allRecords) { + if (isEnabled === true || isEnabled === false) { + const isLogicallyEnabled = await BridgeStore.isLogicallyEnabled(bridgeRecord, requestingUserId); + if (isLogicallyEnabled !== isEnabled) continue; + } + + const bridgeConfig = await BridgeStore.getConfiguration(bridgeRecord, requestingUserId, inRoomId); + enabledBridges.push(new Bridge(bridgeRecord, bridgeConfig)); + } + + return enabledBridges; + } + + public static async setEnabled(type: string, isEnabled: boolean): Promise { + const bridge = await BridgeRecord.findOne({where: {type: type}}); + if (!bridge) throw new Error("Bridge not found"); + + bridge.isEnabled = isEnabled; + return bridge.save(); + } + + public static async setBridgeRoomConfig(_requestingUserId: string, integrationType: string, _inRoomId: string, _newConfig: any): Promise { + const record = await BridgeRecord.findOne({where: {type: integrationType}}); + if (!record) throw new Error("Bridge not found"); + + if (integrationType === "irc") { + throw new Error("IRC Bridges should be modified with the dedicated API"); + } else throw new Error("Unsupported bridge"); + } + + private static async isLogicallyEnabled(record: BridgeRecord, requestingUserId: string): Promise { + if (record.type === "irc") { + const irc = new IrcBridge(requestingUserId); + return irc.hasNetworks(); + } else return true; + } + + private static async getConfiguration(record: BridgeRecord, requestingUserId: string, inRoomId?: string): Promise { + if (record.type === "irc") { + if (!inRoomId) return {}; // The bridge's admin config is handled by other APIs + const irc = new IrcBridge(requestingUserId); + return irc.getRoomConfiguration(inRoomId); + } else return {}; + } + + private constructor() { + } + +} \ No newline at end of file diff --git a/src/db/DimensionStore.ts b/src/db/DimensionStore.ts new file mode 100644 index 0000000..bab7d0e --- /dev/null +++ b/src/db/DimensionStore.ts @@ -0,0 +1,69 @@ +import { Model, Sequelize } from "sequelize-typescript"; +import config from "../config"; +import { LogService } from "matrix-js-snippets"; +import User from "./models/User"; +import UserScalarToken from "./models/UserScalarToken"; +import Upstream from "./models/Upstream"; +import WidgetRecord from "./models/WidgetRecord"; +import * as path from "path"; +import * as Umzug from "umzug"; +import AppService from "./models/AppService"; +import AppServiceUser from "./models/AppServiceUser"; +import NebConfiguration from "./models/NebConfiguration"; +import NebIntegration from "./models/NebIntegration"; +import NebBotUser from "./models/NebBotUser"; +import NebNotificationUser from "./models/NebNotificationUser"; +import NebIntegrationConfig from "./models/NebIntegrationConfig"; +import Webhook from "./models/Webhook"; +import BridgeRecord from "./models/BridgeRecord"; +import IrcBridgeRecord from "./models/IrcBridgeRecord"; +import IrcBridgeNetwork from "./models/IrcBridgeNetwork"; + +class _DimensionStore { + private sequelize: Sequelize; + + constructor() { + this.sequelize = new Sequelize({ + dialect: 'sqlite', + database: "dimension", + storage: config.database.file, + username: "", + password: "", + logging: i => LogService.verbose("DimensionStore [SQL]", i) + }); + this.sequelize.addModels(>[ + User, + UserScalarToken, + Upstream, + WidgetRecord, + AppService, + AppServiceUser, + NebConfiguration, + NebIntegration, + NebBotUser, + NebNotificationUser, + NebIntegrationConfig, + Webhook, + BridgeRecord, + IrcBridgeRecord, + IrcBridgeNetwork, + ]); + } + + public updateSchema(): Promise { + LogService.info("DimensionStore", "Updating schema..."); + + const migrator = new Umzug({ + storage: "sequelize", + storageOptions: {sequelize: this.sequelize}, + migrations: { + params: [this.sequelize.getQueryInterface()], + path: path.join(__dirname, "migrations"), + } + }); + + return migrator.up(); + } +} + +export const DimensionStore = new _DimensionStore(); \ No newline at end of file diff --git a/src/db/NebStore.ts b/src/db/NebStore.ts new file mode 100644 index 0000000..45a2fe0 --- /dev/null +++ b/src/db/NebStore.ts @@ -0,0 +1,353 @@ +import { NebConfig } from "../models/neb"; +import NebConfiguration from "./models/NebConfiguration"; +import NebIntegration from "./models/NebIntegration"; +import Upstream from "./models/Upstream"; +import AppService from "./models/AppService"; +import { LogService } from "matrix-js-snippets"; +import { NebClient } from "../neb/NebClient"; +import NebBotUser from "./models/NebBotUser"; +import NebNotificationUser from "./models/NebNotificationUser"; +import { AppserviceStore } from "./AppserviceStore"; +import config from "../config"; +import { SimpleBot } from "../integrations/SimpleBot"; +import { NebProxy } from "../neb/NebProxy"; +import { ComplexBot } from "../integrations/ComplexBot"; + +export interface SupportedIntegration { + type: string; + name: string; + avatarUrl: string; + description: string; +} + +export class NebStore { + + private static INTEGRATIONS_MODULAR_SUPPORTED = ["giphy", "guggy", "github", "google", "imgur", "rss", "travisci", "wikipedia"]; + + private static INTEGRATIONS = { + // TODO: Support Circle CI + // "circleci": { + // name: "Circle CI", + // avatarUrl: "/img/avatars/circleci.png", + // description: "Announces build results from Circle CI to the room.", + // simple: false, + // }, + "echo": { + name: "Echo", + avatarUrl: "/img/avatars/echo.png", // TODO: Make this image + description: "Repeats text given to it from !echo", + simple: true, + }, + "giphy": { + name: "Giphy", + avatarUrl: "/img/avatars/giphy.png", + description: "Posts a GIF from Giphy using !giphy ", + simple: true, + }, + "guggy": { + name: "Guggy", + avatarUrl: "/img/avatars/guggy.png", + description: "Send a reaction GIF using !guggy ", + simple: true, + }, + // TODO: Support Github + // "github": { + // name: "Github", + // avatarUrl: "/img/avatars/github.png", + // description: "Github issue management and announcements for a repository", + // simple: false, + // }, + "google": { + name: "Google", + avatarUrl: "/img/avatars/google.png", + description: "Searches Google Images using !google image ", + simple: true, + }, + "imgur": { + name: "Imgur", + avatarUrl: "/img/avatars/imgur.png", + description: "Searches and posts images from Imgur using !imgur ", + simple: true, + }, + // TODO: Support JIRA + // "jira": { + // name: "Jira", + // avatarUrl: "/img/avatars/jira.png", + // description: "Jira issue management and announcements for a project", + // simple: false, + // }, + "rss": { + name: "RSS", + avatarUrl: "/img/avatars/rssbot.png", + description: "Announces changes to RSS feeds in the room", + simple: false, + }, + "travisci": { + name: "Travis CI", + avatarUrl: "/img/avatars/travisci.png", + description: "Announces build results from Travis CI to the room", + simple: false, + }, + "wikipedia": { + name: "Wikipedia", + avatarUrl: "/img/avatars/wikipedia.png", + description: "Searches wikipedia using !wikipedia ", + simple: true, + }, + }; + + private static async listEnabledNebBots(simple: boolean): Promise<{ neb: NebConfig, integration: NebIntegration }[]> { + const nebConfigs = await NebStore.getAllConfigs(); + const integrations: { neb: NebConfig, integration: NebIntegration }[] = []; + const hasTypes: string[] = []; + + for (const neb of nebConfigs) { + for (const integration of neb.dbIntegrations) { + if (!integration.isEnabled) continue; + + const metadata = NebStore.INTEGRATIONS[integration.type]; + if (!metadata || metadata.simple !== simple) continue; + if (hasTypes.indexOf(integration.type) !== -1) continue; + + integrations.push({neb, integration}); + hasTypes.push(integration.type); + } + } + + return integrations; + } + + public static async listEnabledNebSimpleBots(): Promise<{ neb: NebConfig, integration: NebIntegration }[]> { + return NebStore.listEnabledNebBots(true); + } + + public static async listEnabledNebComplexBots(): Promise<{ neb: NebConfig, integration: NebIntegration }[]> { + return NebStore.listEnabledNebBots(false); + } + + public static async listSimpleBots(requestingUserId: string): Promise { + const rawIntegrations = await NebStore.listEnabledNebSimpleBots(); + return Promise.all(rawIntegrations.map(async i => { + const proxy = new NebProxy(i.neb, requestingUserId); + return new SimpleBot(i.integration, await proxy.getBotUserId(i.integration)); + })); + } + + public static async listComplexBots(requestingUserId: string, roomId: string): Promise { + const rawIntegrations = await NebStore.listEnabledNebComplexBots(); + return Promise.all(rawIntegrations.map(async i => { + const proxy = new NebProxy(i.neb, requestingUserId); + const notifUserId = await proxy.getNotificationUserId(i.integration, roomId); + const botUserId = null; // TODO: For github + const botConfig = await proxy.getServiceConfiguration(i.integration, roomId); + return new ComplexBot(i.integration, notifUserId, botUserId, botConfig); + })); + } + + public static async setComplexBotConfig(requestingUserId: string, type: string, roomId: string, newConfig: any): Promise { + const rawIntegrations = await NebStore.listEnabledNebComplexBots(); + const integration = rawIntegrations.find(i => i.integration.type === type); + if (!integration) throw new Error("Integration not found"); + + const proxy = new NebProxy(integration.neb, requestingUserId); + return proxy.setServiceConfiguration(integration.integration, roomId, newConfig); + } + + public static async removeSimpleBot(type: string, roomId: string, requestingUserId: string): Promise { + const rawIntegrations = await NebStore.listEnabledNebSimpleBots(); + const integration = rawIntegrations.find(i => i.integration.type === type); + if (!integration) throw new Error("Integration not found"); + + const proxy = new NebProxy(integration.neb, requestingUserId); + return proxy.removeBotFromRoom(integration.integration, roomId); + } + + public static async getAllConfigs(): Promise { + const configs = await NebConfiguration.findAll(); + return Promise.all((configs || []).map(c => NebStore.getConfig(c.id))); + } + + public static async getConfig(id: number): Promise { + const nebConfig = await NebConfiguration.findByPrimary(id); + if (!nebConfig) throw new Error("Configuration not found"); + + const integrations = await NebIntegration.findAll({where: {nebId: id}}); + const fullIntegrations = await NebStore.getCompleteIntegrations(nebConfig, integrations); + + return new NebConfig(nebConfig, fullIntegrations); + } + + public static async createForUpstream(upstreamId: number): Promise { + const upstream = await Upstream.findByPrimary(upstreamId); + if (!upstream) throw new Error("Upstream not found"); + + const nebConfig = await NebConfiguration.create({ + upstreamId: upstream.id, + }); + + return NebStore.getConfig(nebConfig.id); + } + + public static async createForAppservice(appserviceId: string, adminUrl: string): Promise { + const appservice = await AppService.findByPrimary(appserviceId); + if (!appservice) throw new Error("Appservice not found"); + + const nebConfig = await NebConfiguration.create({ + appserviceId: appservice.id, + adminUrl: adminUrl, + }); + + return NebStore.getConfig(nebConfig.id); + } + + public static async getOrCreateIntegration(configurationId: number, integrationType: string): Promise { + if (!NebStore.INTEGRATIONS[integrationType]) throw new Error("Integration not supported"); + + const nebConfig = await NebConfiguration.findByPrimary(configurationId); + if (!nebConfig) throw new Error("Configuration not found"); + + let integration = await NebIntegration.findOne({where: {nebId: nebConfig.id, type: integrationType}}); + if (!integration) { + LogService.info("NebStore", "Creating integration " + integrationType + " for NEB " + configurationId); + integration = await NebIntegration.create({ + type: integrationType, + name: NebStore.INTEGRATIONS[integrationType].name, + avatarUrl: NebStore.INTEGRATIONS[integrationType].avatarUrl, + description: NebStore.INTEGRATIONS[integrationType].description, + isEnabled: false, + isPublic: true, + nebId: configurationId, + }); + } + + return integration; + } + + public static async getCompleteIntegrations(nebConfig: NebConfiguration, knownIntegrations: NebIntegration[]): Promise { + const supported = NebStore.getSupportedIntegrations(nebConfig); + const notSupported: SupportedIntegration[] = []; + for (const supportedIntegration of supported) { + let isSupported = false; + for (const integration of knownIntegrations) { + if (integration.type === supportedIntegration.type) { + isSupported = true; + break; + } + } + + if (!isSupported) notSupported.push(supportedIntegration); + } + + const promises = []; + for (const missingIntegration of notSupported) { + promises.push(NebStore.getOrCreateIntegration(nebConfig.id, missingIntegration.type)); + } + + return Promise.all(promises).then(addedIntegrations => (addedIntegrations || []).concat(knownIntegrations)); + } + + public static getSupportedIntegrations(nebConfig: NebConfiguration): SupportedIntegration[] { + const result = []; + + for (const type of Object.keys(NebStore.INTEGRATIONS)) { + if (nebConfig.upstreamId && NebStore.INTEGRATIONS_MODULAR_SUPPORTED.indexOf(type) === -1) continue; + + const integrationConfig = JSON.parse(JSON.stringify(NebStore.INTEGRATIONS[type])); + integrationConfig["type"] = type; + result.push(integrationConfig); + } + + return result; + } + + public static async setIntegrationEnabled(configurationId: number, integrationType: string, isEnabled: boolean): Promise { + const integration = await this.getOrCreateIntegration(configurationId, integrationType); + integration.isEnabled = isEnabled; + await integration.save(); + + const neb = await this.getConfig(configurationId); + if (!neb.appserviceId) return; // Done - nothing to do from here + const client = new NebClient(neb); + + const botUsers = await NebBotUser.findAll({where: {integrationId: integration.id}}); + for (const user of botUsers) { + await client.updateUser(user.appserviceUserId, isEnabled, true); + } + + const notificationUsers = await NebNotificationUser.findAll({where: {integrationId: integration.id}}); + for (const user of notificationUsers) { + await client.updateUser(user.appserviceUserId, isEnabled, false); + } + } + + public static async getOrCreateBotUser(configurationId: number, integrationType: string): Promise { + const neb = await NebStore.getConfig(configurationId); + if (!neb.appserviceId) throw new Error("Instance not bound to an appservice"); + + const integration = await this.getOrCreateIntegration(configurationId, integrationType); + const users = await NebBotUser.findAll({where: {integrationId: integration.id}}); + if (!users || users.length === 0) { + const appservice = await AppserviceStore.getAppservice(neb.appserviceId); + const userId = "@" + appservice.userPrefix + "_" + integrationType + ":" + config.homeserver.name; + const appserviceUser = await AppserviceStore.getOrCreateUser(neb.appserviceId, userId); + + const client = new NebClient(neb); + await client.updateUser(userId, integration.isEnabled, true); // creates the user in go-neb + + const serviceId = appservice.id + "_integration_" + integrationType; + return NebBotUser.create({ + serviceId: serviceId, + appserviceUserId: appserviceUser.id, + integrationId: integration.id, + }); + } + + return users[0]; + } + + public static async getOrCreateNotificationUser(configurationId: number, integrationType: string, forUserId: string): Promise { + const neb = await NebStore.getConfig(configurationId); + if (!neb.appserviceId) throw new Error("Instance not bound to an appservice"); + + const integration = await this.getOrCreateIntegration(configurationId, integrationType); + const users = await NebNotificationUser.findAll({where: {integrationId: integration.id, ownerId: forUserId}}); + if (!users || users.length === 0) { + const safeUserId = AppserviceStore.getSafeUserId(forUserId); + const appservice = await AppserviceStore.getAppservice(neb.appserviceId); + const userId = "@" + appservice.userPrefix + "_" + integrationType + "_notifications_" + safeUserId + ":" + config.homeserver.name; + const appserviceUser = await AppserviceStore.getOrCreateUser(neb.appserviceId, userId); + + const client = new NebClient(neb); + await client.updateUser(userId, integration.isEnabled, false); // creates the user in go-neb + + const serviceId = appservice.id + "_integration_" + integrationType + "_notifications_" + safeUserId; + return NebNotificationUser.create({ + serviceId: serviceId, + appserviceUserId: appserviceUser.id, + integrationId: integration.id, + ownerId: forUserId, + }); + } + + return users[0]; + } + + public static async setIntegrationConfig(configurationId: number, integrationType: string, newConfig: any): Promise { + const botUser = await NebStore.getOrCreateBotUser(configurationId, integrationType); + const neb = await NebStore.getConfig(configurationId); + const client = new NebClient(neb); + + return client.setServiceConfig(botUser.serviceId, botUser.appserviceUserId, integrationType, newConfig); + } + + public static async getIntegrationConfig(configurationId: number, integrationType: string): Promise { + const botUser = await NebStore.getOrCreateBotUser(configurationId, integrationType); + const neb = await NebStore.getConfig(configurationId); + const client = new NebClient(neb); + + return client.getServiceConfig(botUser.serviceId); + } + + private constructor() { + } +} \ No newline at end of file diff --git a/src/db/ScalarStore.ts b/src/db/ScalarStore.ts new file mode 100644 index 0000000..812e2bd --- /dev/null +++ b/src/db/ScalarStore.ts @@ -0,0 +1,48 @@ +import UserScalarToken from "./models/UserScalarToken"; +import { LogService } from "matrix-js-snippets"; +import Upstream from "./models/Upstream"; +import User from "./models/User"; + +export class ScalarStore { + + public static async doesUserHaveTokensForAllUpstreams(userId: string): Promise { + const scalarTokens = await UserScalarToken.findAll({where: {userId: userId}}); + const upstreamTokenIds = scalarTokens.filter(t => !t.isDimensionToken).map(t => t.upstreamId); + const hasDimensionToken = scalarTokens.filter(t => t.isDimensionToken).length >= 1; + + if (!hasDimensionToken) { + LogService.warn("ScalarStore", "User " + userId + " is missing a Dimension scalar token"); + return false; + } + + const upstreams = await Upstream.findAll(); + for (const upstream of upstreams) { + if (upstreamTokenIds.indexOf(upstream.id) === -1) { + LogService.warn("ScalarStore", "user " + userId + " is missing a scalar token for upstream " + upstream.id + " (" + upstream.name + ")"); + return false; + } + } + + return true; + } + + public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise { + const tokens = await UserScalarToken.findAll({ + where: {isDimensionToken: true, scalarToken: scalarToken}, + include: [User] + }); + if (!tokens || tokens.length === 0) throw new Error("Invalid token"); + + const user = tokens[0].user; + if (ignoreUpstreams) return user; // skip upstreams check + + const hasAllTokens = await ScalarStore.doesUserHaveTokensForAllUpstreams(user.userId); + if (!hasAllTokens) { + throw new Error("Invalid token"); // They are missing an upstream, so we'll lie and say they are not authorized + } + return user; + } + + private constructor() { + } +} \ No newline at end of file diff --git a/src/db/WidgetStore.ts b/src/db/WidgetStore.ts new file mode 100644 index 0000000..8626dd9 --- /dev/null +++ b/src/db/WidgetStore.ts @@ -0,0 +1,33 @@ +import WidgetRecord from "./models/WidgetRecord"; +import { Widget } from "../integrations/Widget"; + +export class WidgetStore { + + public static async listAll(isEnabled?: boolean): Promise { + let conditions = {}; + if (isEnabled === true || isEnabled === false) conditions = {where: {isEnabled: isEnabled}}; + return (await WidgetRecord.findAll(conditions)).map(w => new Widget(w)); + } + + public static async setEnabled(type: string, isEnabled: boolean): Promise { + const widget = await WidgetRecord.findOne({where: {type: type}}); + if (!widget) throw new Error("Widget not found"); + + widget.isEnabled = isEnabled; + return widget.save(); + } + + public static async setOptions(type: string, options: any): Promise { + const optionsJson = JSON.stringify(options); + + const widget = await WidgetRecord.findOne({where: {type: type}}); + if (!widget) throw new Error("Widget not found"); + + widget.optionsJson = optionsJson; + return await widget.save(); + } + + private constructor() { + } + +} \ No newline at end of file diff --git a/src/db/migrations/20171218203245-AddTables.ts b/src/db/migrations/20171218203245-AddTables.ts new file mode 100644 index 0000000..d967582 --- /dev/null +++ b/src/db/migrations/20171218203245-AddTables.ts @@ -0,0 +1,41 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_users", { + "userId": {type: DataType.STRING, primaryKey: true, allowNull: false}, + })) + .then(() => queryInterface.createTable("dimension_upstreams", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "name": {type: DataType.STRING, allowNull: false}, + "type": {type: DataType.STRING, allowNull: false}, + "scalarUrl": {type: DataType.STRING, allowNull: false}, + "apiUrl": {type: DataType.STRING, allowNull: false}, + })) + .then(() => queryInterface.createTable("dimension_scalar_tokens", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "userId": { + type: DataType.STRING, + allowNull: false, + references: {model: "dimension_users", key: "userId"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "scalarToken": {type: DataType.STRING, allowNull: false}, + "isDimensionToken": {type: DataType.BOOLEAN, allowNull: false}, + "upstreamId": { + type: DataType.INTEGER, + allowNull: true, + references: {model: "dimension_upstreams", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + })); + }, + down: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.dropTable("dimension_users")) + .then(() => queryInterface.dropTable("dimension_upstreams")) + .then(() => queryInterface.dropTable("dimension_scalar_tokens")); + } +} \ No newline at end of file diff --git a/src/db/migrations/20171218203245-AddWidgets.ts b/src/db/migrations/20171218203245-AddWidgets.ts new file mode 100644 index 0000000..19b51c7 --- /dev/null +++ b/src/db/migrations/20171218203245-AddWidgets.ts @@ -0,0 +1,81 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_widgets", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "type": {type: DataType.STRING, allowNull: false}, + "name": {type: DataType.STRING, allowNull: false}, + "avatarUrl": {type: DataType.STRING, allowNull: false}, + "description": {type: DataType.STRING, allowNull: false}, + "isEnabled": {type: DataType.BOOLEAN, allowNull: false}, + "isPublic": {type: DataType.BOOLEAN, allowNull: false}, + "optionsJson": {type: DataType.STRING, allowNull: true}, + })) + .then(() => queryInterface.bulkInsert("dimension_widgets", [ + { + type: "custom", + name: "Custom Widget", + avatarUrl: "/img/avatars/customwidget.png", + isEnabled: true, + isPublic: true, + description: "A webpage embedded in the room.", + }, + { + type: "etherpad", + name: "Etherpad", + avatarUrl: "/img/avatars/etherpad.png", + isEnabled: true, + isPublic: true, + description: "Collaborate on documents with members of your room.", + optionsJson: '{"defaultUrl":"https://demo.riot.im/etherpad/p/$roomId_$padName"}', + }, + { + type: "googlecalendar", + name: "Google Calendar", + isEnabled: true, + isPublic: true, + avatarUrl: "/img/avatars/googlecalendar.png", + description: "Share upcoming events in your room with a Google Calendar.", + }, + { + type: "googledocs", + name: "Google Docs", + isEnabled: true, + isPublic: true, + avatarUrl: "/img/avatars/googledocs.png", + description: "Collaborate on and share documents using Google Docs.", + }, + { + type: "youtube", + name: "YouTube Video", + isEnabled: true, + isPublic: true, + avatarUrl: "/img/avatars/youtube.png", + description: "Embed a YouTube, Vimeo, or DailyMotion video in your room.", + }, + { + type: "twitch", + name: "Twitch Livestream", + isEnabled: true, + isPublic: true, + avatarUrl: "/img/avatars/twitch.png", + description: "Embed a Twitch livestream into your room.", + }, + { + type: "jitsi", + name: "Jitsi Conference", + isEnabled: true, + isPublic: true, + avatarUrl: "/img/avatars/jitsi.png", + description: "Hold a video conference with Jitsi Meet", + optionsJson: '{"jitsiDomain":"jitsi.riot.im", "scriptUrl":"https://jitsi.riot.im/libs/external_api.min.js"}', + }, + ])); + }, + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("dimension_widgets"); + } +} \ No newline at end of file diff --git a/src/db/migrations/20171224122045-AddAppservices.ts b/src/db/migrations/20171224122045-AddAppservices.ts new file mode 100644 index 0000000..d64f5b0 --- /dev/null +++ b/src/db/migrations/20171224122045-AddAppservices.ts @@ -0,0 +1,17 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_appservice", { + "id": {type: DataType.STRING, primaryKey: true, allowNull: false}, + "hsToken": {type: DataType.STRING, allowNull: false}, + "asToken": {type: DataType.STRING, allowNull: false}, + "userPrefix": {type: DataType.STRING, allowNull: false}, + })); + }, + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("dimension_appservice"); + } +} \ No newline at end of file diff --git a/src/db/migrations/20171224124645-AddAppserviceUsers.ts b/src/db/migrations/20171224124645-AddAppserviceUsers.ts new file mode 100644 index 0000000..4ae1877 --- /dev/null +++ b/src/db/migrations/20171224124645-AddAppserviceUsers.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_appservice_users", { + "id": {type: DataType.STRING, primaryKey: true, allowNull: false}, + "appserviceId": { + type: DataType.STRING, allowNull: false, + references: {model: "dimension_appservice", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "accessToken": {type: DataType.STRING, allowNull: false}, + "displayName": {type: DataType.STRING, allowNull: true}, + "avatarUrl": {type: DataType.STRING, allowNull: true}, + })); + }, + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("dimension_appservice_users"); + } +} \ No newline at end of file diff --git a/src/db/migrations/20171224140745-AddNeb.ts b/src/db/migrations/20171224140745-AddNeb.ts new file mode 100644 index 0000000..42d1720 --- /dev/null +++ b/src/db/migrations/20171224140745-AddNeb.ts @@ -0,0 +1,76 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_neb_configurations", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "appserviceId": { + type: DataType.STRING, allowNull: true, + references: {model: "dimension_appservice", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "upstreamId": { + type: DataType.INTEGER, allowNull: true, + references: {model: "dimension_upstreams", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "adminUrl": {type: DataType.STRING, allowNull: true}, + })) + .then(() => queryInterface.createTable("dimension_neb_integrations", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "type": {type: DataType.STRING, allowNull: false}, + "name": {type: DataType.STRING, allowNull: false}, + "avatarUrl": {type: DataType.STRING, allowNull: false}, + "description": {type: DataType.STRING, allowNull: false}, + "isEnabled": {type: DataType.BOOLEAN, allowNull: false}, + "isPublic": {type: DataType.BOOLEAN, allowNull: false}, + "nebId": { + type: DataType.INTEGER, allowNull: false, + references: {model: "dimension_neb_configurations", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + })) + .then(() => queryInterface.createTable("dimension_neb_bot_users", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "serviceId": {type: DataType.STRING, allowNull: false}, + "appserviceUserId": { + type: DataType.STRING, allowNull: false, + references: {model: "dimension_appservice_users", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "integrationId": { + type: DataType.INTEGER, allowNull: false, + references: {model: "dimension_neb_integrations", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + })) + .then(() => queryInterface.createTable("dimension_neb_notification_users", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "serviceId": {type: DataType.STRING, allowNull: false}, + "ownerId": { + type: DataType.STRING, allowNull: false, + references: {model: "dimension_users", key: "userId"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "appserviceUserId": { + type: DataType.STRING, allowNull: false, + references: {model: "dimension_appservice_users", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "integrationId": { + type: DataType.INTEGER, allowNull: false, + references: {model: "dimension_neb_integrations", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + })); + }, + down: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.dropTable("dimension_neb_notification_users")) + .then(() => queryInterface.dropTable("dimension_neb_bot_users")) + .then(() => queryInterface.dropTable("dimension_neb_integrations")) + .then(() => queryInterface.dropTable("dimension_neb_configurations")); + } +} \ No newline at end of file diff --git a/src/db/migrations/20180326185545-AddNebIntegrationConfig.ts b/src/db/migrations/20180326185545-AddNebIntegrationConfig.ts new file mode 100644 index 0000000..e2f6a86 --- /dev/null +++ b/src/db/migrations/20180326185545-AddNebIntegrationConfig.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_neb_integration_config", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "integrationId": { + type: DataType.INTEGER, allowNull: false, + references: {model: "dimension_neb_integrations", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "roomId": {type: DataType.STRING, allowNull: false}, + "jsonContent": {type: DataType.STRING, allowNull: false}, + })); + }, + down: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.dropTable("dimension_neb_integration_config")); + } +} \ No newline at end of file diff --git a/src/db/migrations/20180330111645-AddWebhooks.ts b/src/db/migrations/20180330111645-AddWebhooks.ts new file mode 100644 index 0000000..d057373 --- /dev/null +++ b/src/db/migrations/20180330111645-AddWebhooks.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_webhooks", { + "hookId": {type: DataType.STRING, primaryKey: true, allowNull: false}, + "ownerUserId": { + type: DataType.STRING, allowNull: false, + references: {model: "dimension_users", key: "userId"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "purposeId": {type: DataType.STRING, allowNull: false}, + "targetUrl": {type: DataType.STRING, allowNull: true}, + })); + }, + down: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.dropTable("dimension_webhooks")); + } +} \ No newline at end of file diff --git a/src/db/migrations/20180330172445-AddBridges.ts b/src/db/migrations/20180330172445-AddBridges.ts new file mode 100644 index 0000000..eb3ff97 --- /dev/null +++ b/src/db/migrations/20180330172445-AddBridges.ts @@ -0,0 +1,30 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_bridges", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "type": {type: DataType.STRING, allowNull: false}, + "name": {type: DataType.STRING, allowNull: false}, + "avatarUrl": {type: DataType.STRING, allowNull: false}, + "description": {type: DataType.STRING, allowNull: false}, + "isEnabled": {type: DataType.BOOLEAN, allowNull: false}, + "isPublic": {type: DataType.BOOLEAN, allowNull: false}, + })) + .then(() => queryInterface.bulkInsert("dimension_bridges", [ + { + type: "irc", + name: "IRC Bridge", + avatarUrl: "/img/avatars/irc.png", + isEnabled: true, + isPublic: true, + description: "Bridges IRC channels to rooms, supporting multiple networks", + }, + ])); + }, + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("dimension_bridges"); + } +} \ No newline at end of file diff --git a/src/db/migrations/20180330211845-AddIrcBridge.ts b/src/db/migrations/20180330211845-AddIrcBridge.ts new file mode 100644 index 0000000..3e53d82 --- /dev/null +++ b/src/db/migrations/20180330211845-AddIrcBridge.ts @@ -0,0 +1,36 @@ +import { QueryInterface } from "sequelize"; +import { DataType } from "sequelize-typescript"; + +export default { + up: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.createTable("dimension_irc_bridges", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "upstreamId": { + type: DataType.INTEGER, allowNull: true, + references: {model: "dimension_upstreams", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "provisionUrl": {type: DataType.STRING, allowNull: true}, + "isEnabled": {type: DataType.BOOLEAN, allowNull: false}, + })) + .then(() => queryInterface.createTable("dimension_irc_bridge_networks", { + "id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false}, + "bridgeId": { + type: DataType.INTEGER, allowNull: false, + references: {model: "dimension_irc_bridges", key: "id"}, + onUpdate: "cascade", onDelete: "cascade", + }, + "bridgeNetworkId": {type: DataType.STRING, allowNull: false}, + "bridgeUserId": {type: DataType.STRING, allowNull: false}, + "displayName": {type: DataType.STRING, allowNull: false}, + "domain": {type: DataType.STRING, allowNull: false}, + "isEnabled": {type: DataType.BOOLEAN, allowNull: false}, + })); + }, + down: (queryInterface: QueryInterface) => { + return Promise.resolve() + .then(() => queryInterface.dropTable("dimension_irc_bridges")) + .then(() => queryInterface.dropTable("dimension_irc_bridge_networks")); + } +} \ No newline at end of file diff --git a/src/db/models/AppService.ts b/src/db/models/AppService.ts new file mode 100644 index 0000000..2d6e746 --- /dev/null +++ b/src/db/models/AppService.ts @@ -0,0 +1,21 @@ +import { Column, Model, PrimaryKey, Table } from "sequelize-typescript"; + +@Table({ + tableName: "dimension_appservice", + underscoredAll: false, + timestamps: false, +}) +export default class AppService extends Model { + @PrimaryKey + @Column + id: string; + + @Column + hsToken: string; + + @Column + asToken: string; + + @Column + userPrefix: string; +} \ No newline at end of file diff --git a/src/db/models/AppServiceUser.ts b/src/db/models/AppServiceUser.ts new file mode 100644 index 0000000..c7eb137 --- /dev/null +++ b/src/db/models/AppServiceUser.ts @@ -0,0 +1,28 @@ +import { AllowNull, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import AppService from "./AppService"; + +@Table({ + tableName: "dimension_appservice_users", + underscoredAll: false, + timestamps: false, +}) +export default class AppServiceUser extends Model { + @PrimaryKey + @Column + id: string; + + @Column + accessToken: string; + + @AllowNull + @Column + avatarUrl?: string; + + @AllowNull + @Column + displayName?: string; + + @Column + @ForeignKey(() => AppService) + appserviceId: string; +} \ No newline at end of file diff --git a/src/db/models/BridgeRecord.ts b/src/db/models/BridgeRecord.ts new file mode 100644 index 0000000..facc328 --- /dev/null +++ b/src/db/models/BridgeRecord.ts @@ -0,0 +1,32 @@ +import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-typescript"; +import { IntegrationRecord } from "./IntegrationRecord"; + +@Table({ + tableName: "dimension_bridges", + underscoredAll: false, + timestamps: false, +}) +export default class BridgeRecord extends Model implements IntegrationRecord { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + type: string; + + @Column + name: string; + + @Column + avatarUrl: string; + + @Column + description: string; + + @Column + isEnabled: boolean; + + @Column + isPublic: boolean; +} \ No newline at end of file diff --git a/src/db/models/IntegrationRecord.ts b/src/db/models/IntegrationRecord.ts new file mode 100644 index 0000000..2846644 --- /dev/null +++ b/src/db/models/IntegrationRecord.ts @@ -0,0 +1,8 @@ +export interface IntegrationRecord { + type: string; + name: string; + avatarUrl: string; + description: string; + isEnabled: boolean; + isPublic: boolean; +} \ No newline at end of file diff --git a/src/db/models/IrcBridgeNetwork.ts b/src/db/models/IrcBridgeNetwork.ts new file mode 100644 index 0000000..98c272a --- /dev/null +++ b/src/db/models/IrcBridgeNetwork.ts @@ -0,0 +1,34 @@ +import { AllowNull, AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import IrcBridgeRecord from "./IrcBridgeRecord"; + +@Table({ + tableName: "dimension_irc_bridge_networks", + underscoredAll: false, + timestamps: false, +}) +export default class IrcBridgeNetwork extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @AllowNull + @Column + @ForeignKey(() => IrcBridgeRecord) + bridgeId: number; + + @Column + isEnabled: boolean; + + @Column + bridgeNetworkId: string; // the real ID as given by /querynetworks + + @Column + bridgeUserId: string; + + @Column + displayName: string; + + @Column + domain: string; +} \ No newline at end of file diff --git a/src/db/models/IrcBridgeRecord.ts b/src/db/models/IrcBridgeRecord.ts new file mode 100644 index 0000000..167bbba --- /dev/null +++ b/src/db/models/IrcBridgeRecord.ts @@ -0,0 +1,26 @@ +import { AllowNull, AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import Upstream from "./Upstream"; + +@Table({ + tableName: "dimension_irc_bridges", + underscoredAll: false, + timestamps: false, +}) +export default class IrcBridgeRecord extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @AllowNull + @Column + @ForeignKey(() => Upstream) + upstreamId?: number; + + @AllowNull + @Column + provisionUrl?: string; + + @Column + isEnabled: boolean; +} \ No newline at end of file diff --git a/src/db/models/NebBotUser.ts b/src/db/models/NebBotUser.ts new file mode 100644 index 0000000..70d7509 --- /dev/null +++ b/src/db/models/NebBotUser.ts @@ -0,0 +1,26 @@ +import { AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import AppServiceUser from "./AppServiceUser"; +import NebIntegration from "./NebIntegration"; + +@Table({ + tableName: "dimension_neb_bot_users", + underscoredAll: false, + timestamps: false, +}) +export default class NebBotUser extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + serviceId: string; + + @Column + @ForeignKey(() => AppServiceUser) + appserviceUserId: string; + + @Column + @ForeignKey(() => NebIntegration) + integrationId: number; +} \ No newline at end of file diff --git a/src/db/models/NebConfiguration.ts b/src/db/models/NebConfiguration.ts new file mode 100644 index 0000000..718607f --- /dev/null +++ b/src/db/models/NebConfiguration.ts @@ -0,0 +1,29 @@ +import { AllowNull, AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import Upstream from "./Upstream"; +import AppService from "./AppService"; + +@Table({ + tableName: "dimension_neb_configurations", + underscoredAll: false, + timestamps: false, +}) +export default class NebConfiguration extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @AllowNull + @Column + adminUrl?: string; + + @AllowNull + @Column + @ForeignKey(() => AppService) + appserviceId?: string; + + @AllowNull + @Column + @ForeignKey(() => Upstream) + upstreamId?: number; +} \ No newline at end of file diff --git a/src/db/models/NebIntegration.ts b/src/db/models/NebIntegration.ts new file mode 100644 index 0000000..84cab20 --- /dev/null +++ b/src/db/models/NebIntegration.ts @@ -0,0 +1,37 @@ +import { AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import { IntegrationRecord } from "./IntegrationRecord"; +import NebConfiguration from "./NebConfiguration"; + +@Table({ + tableName: "dimension_neb_integrations", + underscoredAll: false, + timestamps: false, +}) +export default class NebIntegration extends Model implements IntegrationRecord { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + type: string; + + @Column + name: string; + + @Column + avatarUrl: string; + + @Column + description: string; + + @Column + isEnabled: boolean; + + @Column + isPublic: boolean; + + @Column + @ForeignKey(() => NebConfiguration) + nebId: number; +} \ No newline at end of file diff --git a/src/db/models/NebIntegrationConfig.ts b/src/db/models/NebIntegrationConfig.ts new file mode 100644 index 0000000..dc6b6c9 --- /dev/null +++ b/src/db/models/NebIntegrationConfig.ts @@ -0,0 +1,24 @@ +import { AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import NebIntegration from "./NebIntegration"; + +@Table({ + tableName: "dimension_neb_integration_config", + underscoredAll: false, + timestamps: false, +}) +export default class NebIntegrationConfig extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + @ForeignKey(() => NebIntegration) + integrationId: string; + + @Column + roomId: string; + + @Column + jsonContent: string; +} \ No newline at end of file diff --git a/src/db/models/NebNotificationUser.ts b/src/db/models/NebNotificationUser.ts new file mode 100644 index 0000000..d72dff4 --- /dev/null +++ b/src/db/models/NebNotificationUser.ts @@ -0,0 +1,31 @@ +import { AutoIncrement, Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import AppServiceUser from "./AppServiceUser"; +import NebIntegration from "./NebIntegration"; +import User from "./User"; + +@Table({ + tableName: "dimension_neb_notification_users", + underscoredAll: false, + timestamps: false, +}) +export default class NebNotificationUser extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + serviceId: string; + + @Column + @ForeignKey(() => User) + ownerId: string; + + @Column + @ForeignKey(() => AppServiceUser) + appserviceUserId: string; + + @Column + @ForeignKey(() => NebIntegration) + integrationId: number; +} \ No newline at end of file diff --git a/src/db/models/Upstream.ts b/src/db/models/Upstream.ts new file mode 100644 index 0000000..908a1bc --- /dev/null +++ b/src/db/models/Upstream.ts @@ -0,0 +1,25 @@ +import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-typescript"; + +@Table({ + tableName: "dimension_upstreams", + underscoredAll: false, + timestamps: false, +}) +export default class Upstream extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + name: string; + + @Column + type: string; + + @Column + scalarUrl: string; + + @Column + apiUrl: string; +} \ No newline at end of file diff --git a/src/db/models/User.ts b/src/db/models/User.ts new file mode 100644 index 0000000..36a28f4 --- /dev/null +++ b/src/db/models/User.ts @@ -0,0 +1,14 @@ +import { Column, Model, PrimaryKey, Table } from "sequelize-typescript"; + +@Table({ + tableName: "dimension_users", + underscoredAll: false, + timestamps: false, +}) +export default class User extends Model { + // This is really just a holding class to keep foreign keys alive + + @PrimaryKey + @Column + userId: string; +} \ No newline at end of file diff --git a/src/db/models/UserScalarToken.ts b/src/db/models/UserScalarToken.ts new file mode 100644 index 0000000..e086a04 --- /dev/null +++ b/src/db/models/UserScalarToken.ts @@ -0,0 +1,36 @@ +import { + AllowNull, AutoIncrement, BelongsTo, Column, ForeignKey, Model, PrimaryKey, + Table +} from "sequelize-typescript"; +import User from "./User"; +import Upstream from "./Upstream"; + +@Table({ + tableName: "dimension_scalar_tokens", + underscoredAll: false, + timestamps: false, +}) +export default class UserScalarToken extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + @ForeignKey(() => User) + userId: string; + + @BelongsTo(() => User) + user: User; + + @Column + scalarToken: string; + + @Column + isDimensionToken: boolean; + + @AllowNull + @Column + @ForeignKey(() => Upstream) + upstreamId?: number; +} \ No newline at end of file diff --git a/src/db/models/Webhook.ts b/src/db/models/Webhook.ts new file mode 100644 index 0000000..cbfcd16 --- /dev/null +++ b/src/db/models/Webhook.ts @@ -0,0 +1,25 @@ +import { Column, ForeignKey, Model, PrimaryKey, Table } from "sequelize-typescript"; +import User from "./User"; + +@Table({ + tableName: "dimension_webhooks", + underscoredAll: false, + timestamps: false, +}) +export default class Webhook extends Model { + // This is really just a holding class to keep foreign keys alive + + @PrimaryKey + @Column + hookId: string; + + @Column + @ForeignKey(() => User) + ownerUserId: string; + + @Column + purposeId: string; + + @Column + targetUrl: string; +} \ No newline at end of file diff --git a/src/db/models/WidgetRecord.ts b/src/db/models/WidgetRecord.ts new file mode 100644 index 0000000..eb0437d --- /dev/null +++ b/src/db/models/WidgetRecord.ts @@ -0,0 +1,35 @@ +import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-typescript"; +import { IntegrationRecord } from "./IntegrationRecord"; + +@Table({ + tableName: "dimension_widgets", + underscoredAll: false, + timestamps: false, +}) +export default class WidgetRecord extends Model implements IntegrationRecord { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + type: string; + + @Column + name: string; + + @Column + avatarUrl: string; + + @Column + description: string; + + @Column + isEnabled: boolean; + + @Column + isPublic: boolean; + + @Column + optionsJson: string; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..43e96c0 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,17 @@ +import { LogService } from "matrix-js-snippets"; +import config from "./config"; +import { DimensionStore } from "./db/DimensionStore"; +import Webserver from "./api/Webserver"; +import { CURRENT_VERSION } from "./version"; + +LogService.configure(config.logging); +LogService.info("index", "Starting dimension " + CURRENT_VERSION); + +async function startup() { + await DimensionStore.updateSchema(); + + const webserver = new Webserver(); + await webserver.start(); +} + +startup().then(() => LogService.info("index", "Dimension is ready!")); \ No newline at end of file diff --git a/src/integration/generic_types/Bridge.js b/src/integration/generic_types/Bridge.js deleted file mode 100644 index bb1a463..0000000 --- a/src/integration/generic_types/Bridge.js +++ /dev/null @@ -1,22 +0,0 @@ -var IntegrationStub = require("./IntegrationStub"); - -/** - * Represents a bridge. Normally bridges have enhanced configuration and requirements over bots. - */ -class Bridge extends IntegrationStub { - - /** - * Creates a new bridge - * @param bridgeConfig the configuration for the bridge - */ - constructor(bridgeConfig) { - super(bridgeConfig); - } - - /*override*/ - getUserId() { - return null; // bridges don't have bot users we care about - } -} - -module.exports = Bridge; \ No newline at end of file diff --git a/src/integration/generic_types/ComplexBot.js b/src/integration/generic_types/ComplexBot.js deleted file mode 100644 index fd96b46..0000000 --- a/src/integration/generic_types/ComplexBot.js +++ /dev/null @@ -1,18 +0,0 @@ -var IntegrationStub = require("./IntegrationStub"); - -/** - * Represents a bot with additional configuration or setup needs. Normally indicates a bot needs - * more than a simple invite to the room. - */ -class ComplexBot extends IntegrationStub { - - /** - * Creates a new complex bot - * @param botConfig the configuration for the bot - */ - constructor(botConfig) { - super(botConfig); - } -} - -module.exports = ComplexBot; \ No newline at end of file diff --git a/src/integration/generic_types/IntegrationStub.js b/src/integration/generic_types/IntegrationStub.js deleted file mode 100644 index d867532..0000000 --- a/src/integration/generic_types/IntegrationStub.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Stub for an Integration - */ -class IntegrationStub { - constructor(botConfig) { - this._config = botConfig; - } - - /** - * Gets the user ID for this bot - * @return {Promise} resolves to the user ID - */ - getUserId() { - return Promise.resolve(this._config.userId); - } - - /** - * Gets state information that represents how this bot is operating. - * @return {Promise<*>} resolves to the state information - */ - getState() { - return Promise.resolve({}); - } - - /** - * Removes the integration from the given room - * @param {string} roomId the room ID to remove the integration from - * @returns {Promise<>} resolves when completed - */ - removeFromRoom(roomId) { - throw new Error("Not implemented"); - } - - /** - * Updates the state information for this integration. The data passed is an implementation detail. - * @param {*} newState the new state - * @returns {Promise<*>} resolves when completed, with the new state of the integration - */ - updateState(newState) { - return Promise.resolve({}); - } -} - -module.exports = IntegrationStub; diff --git a/src/integration/generic_types/Widget.js b/src/integration/generic_types/Widget.js deleted file mode 100644 index f6f18e7..0000000 --- a/src/integration/generic_types/Widget.js +++ /dev/null @@ -1,22 +0,0 @@ -var IntegrationStub = require("./IntegrationStub"); - -/** - * Represents a widget. Widgets allow for web applications or rich functionality within the room. - */ -class Widget extends IntegrationStub { - - /** - * Creates a new widget - * @param widgetConfig the configuration for the widget - */ - constructor(widgetConfig) { - super(widgetConfig); - } - - /*override*/ - getUserId() { - return null; // widgets don't have bot users we care about - } -} - -module.exports = Widget; \ No newline at end of file diff --git a/src/integration/impl/StubbedFactory.js b/src/integration/impl/StubbedFactory.js deleted file mode 100644 index 918e7c0..0000000 --- a/src/integration/impl/StubbedFactory.js +++ /dev/null @@ -1,20 +0,0 @@ -var IntegrationStub = require("../generic_types/IntegrationStub"); - -/** - * Creates an integration using the given - * @param {DimensionStore} db the database - * @param {*} integrationConfig the integration configuration - * @param {string} roomId the room ID - * @param {string} scalarToken the scalar token - * @returns {Promise<*>} resolves to the configured integration - */ -var factory = (db, integrationConfig, roomId, scalarToken) => { - factory.validateConfig(integrationConfig); - return Promise.resolve(new IntegrationStub(integrationConfig)); -}; - -factory.validateConfig = (integrationConfig) => { - // Nothing to do -}; - -module.exports = factory; \ No newline at end of file diff --git a/src/integration/impl/circleci/CircleCiBot.js b/src/integration/impl/circleci/CircleCiBot.js deleted file mode 100644 index 0a70bfd..0000000 --- a/src/integration/impl/circleci/CircleCiBot.js +++ /dev/null @@ -1,53 +0,0 @@ -var ComplexBot = require("../../generic_types/ComplexBot"); - -/** - * Represents a CircleCI bot - */ -class CircleCiBot extends ComplexBot { - - /** - * Creates a new CircleCI bot - * @param botConfig the bot configuration - * @param backbone the backbone powering this bot - */ - constructor(botConfig, backbone) { - super(botConfig); - this._backbone = backbone; - } - - /*override*/ - getUserId() { - return this._backbone.getUserId(); - } - - /*override*/ - getState() { - var response = { - repoTemplates: [], - immutableRepoTemplates: [], - webhookUrl: "" - }; - return this._backbone.getRepos().then(templates => { - response.repoTemplates = templates; - return this._backbone.getImmutableRepos(); - }).then(immutable => { - response.immutableRepoTemplates = immutable; - return this._backbone.getWebhookUrl(); - }).then(url => { - response.webhookUrl = url; - return response; - }); - } - - /*override*/ - removeFromRoom(roomId) { - return this._backbone.removeFromRoom(roomId); - } - - /*override*/ - updateState(newState) { - return this._backbone.setRepos(newState.repoTemplates).then(() => this.getState()); - } -} - -module.exports = CircleCiBot; \ No newline at end of file diff --git a/src/integration/impl/circleci/CircleCiFactory.js b/src/integration/impl/circleci/CircleCiFactory.js deleted file mode 100644 index c3e7fa7..0000000 --- a/src/integration/impl/circleci/CircleCiFactory.js +++ /dev/null @@ -1,20 +0,0 @@ -var CircleCiBot = require("./CircleCiBot"); -var VectorCircleCiBackbone = require("./VectorCircleCiBackbone"); -var UpstreamConfiguration = require("../../../UpstreamConfiguration"); - -var factory = (db, integrationConfig, roomId, scalarToken) => { - factory.validateConfig(integrationConfig); - - return db.getUpstreamToken(scalarToken).then(upstreamToken => { - var backbone = new VectorCircleCiBackbone(roomId, upstreamToken); - return new CircleCiBot(integrationConfig, backbone); - }); -}; - -factory.validateConfig = (integrationConfig) => { - if (!integrationConfig.upstream) throw new Error("Unsupported configuration"); - if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream"); - if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified"); -}; - -module.exports = factory; \ No newline at end of file diff --git a/src/integration/impl/circleci/StubbedCircleCiBackbone.js b/src/integration/impl/circleci/StubbedCircleCiBackbone.js deleted file mode 100644 index 0a454db..0000000 --- a/src/integration/impl/circleci/StubbedCircleCiBackbone.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Stubbed/placeholder CircleCI backbone - */ -class StubbedCircleCiBackbone { - - /** - * Creates a new stubbed CircleCI backbone - */ - constructor() { - } - - /** - * Gets the user ID for this backbone - * @returns {Promise} resolves to the user ID - */ - getUserId() { - throw new Error("Not implemented"); - } - - /** - * Gets the repository templates for this backbone - * @returns {Promise<{repoKey:string,template:string}[]>} resolves to the collection of repositories and their templates - */ - getRepos() { - throw new Error("Not implemented"); - } - - /** - * Gets the immutable repository templates for this backbone (set by other users) - * @returns {Promise<{repoKey:string,template:string,ownerId:string}[]>} resolves to the collection of repositories and their templates - */ - getImmutableRepos() { - throw new Error("Not implemented"); - } - - /** - * Sets the new repository templates for this backbone - * @param {{repoKey:string,template:string}[]} newRepos the new templates for the repositories - * @returns {Promise<>} resolves when complete - */ - setRepos(newRepos) { - throw new Error("Not implemented"); - } - - /** - * Gets the webhook url for this backbone - * @returns {Promise} resolves to the webhook URL - */ - getWebhookUrl() { - throw new Error("Not implemented"); - } - - /** - * Removes the bot from the given room - * @param {string} roomId the room ID to remove the bot from - * @returns {Promise<>} resolves when completed - */ - removeFromRoom(roomId) { - throw new Error("Not implemented"); - } -} - -module.exports = StubbedCircleCiBackbone; \ No newline at end of file diff --git a/src/integration/impl/circleci/VectorCircleCiBackbone.js b/src/integration/impl/circleci/VectorCircleCiBackbone.js deleted file mode 100644 index 9b3ee4a..0000000 --- a/src/integration/impl/circleci/VectorCircleCiBackbone.js +++ /dev/null @@ -1,108 +0,0 @@ -var StubbedCircleCiBackbone = require("./StubbedCircleCiBackbone"); -var VectorScalarClient = require("../../../scalar/VectorScalarClient"); -var _ = require("lodash"); -var log = require("../../../util/LogService"); - -/** - * Backbone for CircleCI bots running on vector.im through scalar - */ -class VectorCircleCiBackbone extends StubbedCircleCiBackbone { - - /** - * Creates a new Vector CircleCI backbone - * @param {string} roomId the room ID to manage - * @param {string} upstreamScalarToken the vector scalar token - */ - constructor(roomId, upstreamScalarToken) { - super(); - this._roomId = roomId; - this._scalarToken = upstreamScalarToken; - this._info = null; - this._otherTemplates = []; - } - - /*override*/ - getUserId() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - return this._info.bot_user_id; - }); - } - - /*override*/ - getRepos() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - if (this._info.integrations.length == 0) return []; - - var rooms = _.keys(this._info.integrations[0].config.rooms); - if (rooms.indexOf(this._roomId) === -1) return []; - - var repos = _.keys(this._info.integrations[0].config.rooms[this._roomId].repos); - return _.map(repos, r => { - return {repoKey: r, template: this._info.integrations[0].config.rooms[this._roomId].repos[r].template}; - }); - }); - } - - /*override*/ - getImmutableRepos() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - return this._otherTemplates; - }); - } - - /*override*/ - setRepos(newRepos) { - var config = {}; - config[this._roomId] = {repos: {}}; - for (var repo of newRepos) config[this._roomId].repos[repo.repoKey] = {template: repo.template}; - - return VectorScalarClient.configureIntegration("circleci", this._scalarToken, { - rooms: config - }); - } - - /*override*/ - getWebhookUrl() { - // string - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - if (this._info.integrations.length == 0) return ""; - return this._info.integrations[0].config.webhook_url; - }); - } - - _getInfo() { - return VectorScalarClient.getIntegrationsForRoom(this._roomId, this._scalarToken).then(integrations => { - this._otherTemplates = []; - for (var integration of integrations) { - if (integration.self) continue; // skip - we're not looking for ones we know about - if (integration.type == "circleci") { - var roomIds = _.keys(integration.config.rooms); - if (roomIds.length === 0) continue; - if (roomIds.length !== 1) log.warn("VectorCircleCiBackbone", "Expected 1 room but found " + roomIds.length); - - var roomConfig = integration.config.rooms[roomIds[0]]; - var repositories = _.keys(roomConfig.repos); - - for (var repo of repositories) { - this._otherTemplates.push({ - repoKey: repo, - template: roomConfig.repos[repo].template, - ownerId: integration.user_id - }); - } - } - } - - return VectorScalarClient.getIntegration("circleci", this._roomId, this._scalarToken); - }).then(info => { - this._info = info; - }); - } - - /*override*/ - removeFromRoom(roomId) { - return VectorScalarClient.removeIntegration("circleci", roomId, this._scalarToken); - } -} - -module.exports = VectorCircleCiBackbone; \ No newline at end of file diff --git a/src/integration/impl/index.js b/src/integration/impl/index.js deleted file mode 100644 index 92d7e9b..0000000 --- a/src/integration/impl/index.js +++ /dev/null @@ -1,46 +0,0 @@ -var log = require("../../util/LogService"); -var StubbedFactory = require("./StubbedFactory"); -var SimpleBotFactory = require("./simple_bot/SimpleBotFactory"); -var RSSFactory = require("./rss/RSSFactory"); -var IRCFactory = require("./irc/IRCFactory"); -var TravisCiFactory = require("./travisci/TravisCiFactory"); -var CircleCiFactory = require("./circleci/CircleCiFactory"); -var SimpleWidgetFactory = require("./simple_widget/SimpleWidgetFactory"); - -var mapping = { - "complex-bot": { - "rss": RSSFactory, - "travisci": TravisCiFactory, - "circleci": CircleCiFactory, - }, - "bridge": { - "irc": IRCFactory, - } -}; - -var defaultFactories = { - "complex-bot": null, - "bot": SimpleBotFactory, - "bridge": null, - "widget": SimpleWidgetFactory, -}; - -module.exports = { - getFactory: (integrationConfig) => { - var opts = mapping[integrationConfig.type]; - - if (!opts) { - log.verbose("IntegrationImpl", "No option set available for " + integrationConfig.type + " - will attempt defaults"); - } - - var factory = null; - if (!opts) factory = defaultFactories[integrationConfig.type]; - else factory = opts[integrationConfig.integrationType]; - if (!factory) { - log.verbose("IntegrationImpl", "No factory available for " + integrationConfig.type + " (" + integrationConfig.integrationType + ") - using stub"); - factory = StubbedFactory; - } - - return factory; - } -}; \ No newline at end of file diff --git a/src/integration/impl/irc/IRCApi.js b/src/integration/impl/irc/IRCApi.js deleted file mode 100644 index aed963d..0000000 --- a/src/integration/impl/irc/IRCApi.js +++ /dev/null @@ -1,114 +0,0 @@ -var Integrations = require("../../index"); -var IntegrationImpl = require("../index"); -var log = require("../../../util/LogService"); - -/** - * API Handler for the IRC integration - */ -class IRCApi { - - /** - * Creates a new IRC API - */ - constructor() { - } - - /** - * Bootstraps the IRC API - * @param {*} app the Express application - * @param {DimensionStore} db the store to use - */ - bootstrap(app, db) { - if (!Integrations.byType["bridge"] || !Integrations.byType["bridge"]["irc"]) { - log.info("IRCApi", "IRC Bridge not enabled - not setting up the API"); - return; - } else log.info("IRCApi", "Setting up IRC API"); - - this._db = db; - - app.get("/api/v1/irc/:roomId/ops/:network/:channel", this._getChannelOps.bind(this)); - app.put("/api/v1/irc/:roomId/channels/:network/:channel", this._addChannel.bind(this)); - app.delete("/api/v1/irc/:roomId/channels/:network/:channel", this._deleteChannel.bind(this)); - } - - _getChannelOps(req, res) { - this._generalProcessing(req, res).then(ircBridge => { - var network = req.params.network; - var channel = req.params.channel; - return ircBridge.getChannelOps(network, channel).catch(err => { - log.error("IRCApi", err); - console.error(err); - res.status(500).send({error: err}); - return null; - }); - }).then(ops => { - if (ops !== null) res.status(200).send(ops); - }).catch(() => null); - } - - _addChannel(req, res) { - this._generalProcessing(req, res).then(ircBridge => { - var network = req.params.network; - var channel = req.params.channel; - var op = req.query.op; - return ircBridge.addChannel(network, channel, op).catch(err => { - log.error("IRCApi", err); - console.error(err); - res.status(500).send({error: err}); - return null; - }); - }).then(result => { - if (result !== null) res.status(200).send({successful: true}); - }).catch(() => null); - } - - _deleteChannel(req, res) { - this._generalProcessing(req, res).then(ircBridge => { - var network = req.params.network; - var channel = req.params.channel; - return ircBridge.removeChannel(network, channel).catch(err => { - log.error("IRCApi", err); - console.error(err); - res.status(500).send({error: err}); - return null; - }); - }).then(result => { - if (result !== null) res.status(200).send({successful: true}); - }).catch(() => null); - } - - _generalProcessing(req, res) { - return new Promise((resolve, reject) => { - res.setHeader("Content-Type", "application/json"); - - var roomId = req.params.roomId; - var network = req.params.network; - var channel = req.params.channel; - if (!roomId || !network || !channel) { - res.status(400).send({error: 'Missing room ID, network, or channel'}); - reject(); - return; - } - - var scalarToken = req.query.scalar_token; - this._db.checkToken(scalarToken).then(() => { - var conf = Integrations.byType["bridge"]["irc"]; - var factory = IntegrationImpl.getFactory(conf); - factory(this._db, conf, roomId, scalarToken).then(resolve).catch(err => { - log.error("IRCApi", err); - console.error(err); - res.status(500).send({error: err}); - reject(); - }); - }).catch(err => { - log.error("IRCApi", err); - console.error(err); - res.status(500).send({error: err}); - reject(); - }); - }); - } - -} - -module.exports = new IRCApi(); \ No newline at end of file diff --git a/src/integration/impl/irc/IRCBridge.js b/src/integration/impl/irc/IRCBridge.js deleted file mode 100644 index 95a20bc..0000000 --- a/src/integration/impl/irc/IRCBridge.js +++ /dev/null @@ -1,75 +0,0 @@ -var Bridge = require("../../generic_types/Bridge"); - -/** - * Represents an IRC bridge - */ -class IRCBridge extends Bridge { - - /** - * Creates a new IRC bridge - * @param bridgeConfig the bridge configuration - * @param backbone the backbone powering this bridge - */ - constructor(bridgeConfig, backbone) { - super(bridgeConfig); - this._backbone = backbone; - } - - /*override*/ - getState() { - var response = { - availableNetworks: [], - channels: {} - }; - return this._backbone.getNetworks().then(networks => { - response.availableNetworks = networks; - return this._backbone.getLinkedChannels(); - }).then(channels => { - response.channels = channels; - return response; - }); - } - - /*override*/ - removeFromRoom(roomId) { - return this._backbone.removeFromRoom(roomId); - } - - /*override*/ - updateState(newState) { - throw new Error("State cannot be updated for an IRC bridge. Use the IRC API instead."); - } - - /** - * Gets a list of operators available in a particular channel on a particular network - * @param {string} network the network to look at - * @param {string} channel the channel to look in (without prefixed #) - * @returns {Promise} resolves to a list of operators - */ - getChannelOps(network, channel) { - return this._backbone.getChannelOps(network, channel); - } - - /** - * Links a channel to the room this bridge controls - * @param {string} network the network to link to - * @param {string} channel the channel to link to - * @param {string} op the channel operator to request permission from - * @returns {Promise<>} resolves when complete - */ - addChannel(network, channel, op) { - return this._backbone.addChannel(network, channel, op); - } - - /** - * Unlinks a channel from the room this bridge controls - * @param {string} network the network to unlink from - * @param {string} channel the channel to unlink - * @returns {Promise<>} resolves when complete - */ - removeChannel(network, channel) { - return this._backbone.removeChannel(network, channel); - } -} - -module.exports = IRCBridge; \ No newline at end of file diff --git a/src/integration/impl/irc/IRCFactory.js b/src/integration/impl/irc/IRCFactory.js deleted file mode 100644 index 131d729..0000000 --- a/src/integration/impl/irc/IRCFactory.js +++ /dev/null @@ -1,20 +0,0 @@ -var IRCBridge = require("./IRCBridge"); -var VectorIrcBackbone = require("./VectorIrcBackbone"); -var UpstreamConfiguration = require("../../../UpstreamConfiguration"); - -var factory = (db, integrationConfig, roomId, scalarToken) => { - factory.validateConfig(integrationConfig); - - return db.getUpstreamToken(scalarToken).then(upstreamToken => { - var backbone = new VectorIrcBackbone(roomId, upstreamToken); - return new IRCBridge(integrationConfig, backbone); - }); -}; - -factory.validateConfig = (integrationConfig) => { - if (!integrationConfig.upstream) throw new Error("Unsupported configuration"); - if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream"); - if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified"); -}; - -module.exports = factory; \ No newline at end of file diff --git a/src/integration/impl/irc/StubbedIrcBackbone.js b/src/integration/impl/irc/StubbedIrcBackbone.js deleted file mode 100644 index 2b323d1..0000000 --- a/src/integration/impl/irc/StubbedIrcBackbone.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Stubbed/placeholder IRC backbone - */ -class StubbedIrcBackbone { - - /** - * Creates a new stubbed IRC backbone - */ - constructor() { - } - - /** - * Gets a list of all available networks - * @returns {Promise<{name: string, id: string}[]>} resolves to the list of available networks - */ - getNetworks() { - return Promise.resolve([]); - } - - /** - * Gets a network representation of the linked channels - * @returns {Promise<{[string]: string[]}>} resolves to the network representation of linked channels - */ - getLinkedChannels() { - return Promise.resolve({}); - } - - /** - * Gets a list of operators available in a particular channel on a particular network - * @param {string} network the network to look at - * @param {string} channel the channel to look in (without prefixed #) - * @returns {Promise} resolves to a list of operators - */ - getChannelOps(network, channel) { - return Promise.resolve([]); - } - - /** - * Links a channel to the room this backbone controls - * @param {string} network the network to link to - * @param {string} channel the channel to link to - * @param {string} op the channel operator to request permission from - * @returns {Promise<>} resolves when complete - */ - addChannel(network, channel, op) { - throw new Error("Not implemented"); - } - - /** - * Unlinks a channel from the room this backbone controls - * @param {string} network the network to unlink from - * @param {string} channel the channel to unlink - * @returns {Promise<>} resolves when complete - */ - removeChannel(network, channel) { - throw new Error("Not implemented"); - } -} - -module.exports = StubbedIrcBackbone; \ No newline at end of file diff --git a/src/integration/impl/irc/VectorIrcBackbone.js b/src/integration/impl/irc/VectorIrcBackbone.js deleted file mode 100644 index 65165ae..0000000 --- a/src/integration/impl/irc/VectorIrcBackbone.js +++ /dev/null @@ -1,120 +0,0 @@ -var StubbedIrcBackbone = require("./StubbedIrcBackbone"); -var VectorScalarClient = require("../../../scalar/VectorScalarClient"); -var _ = require("lodash"); -var log = require("../../../util/LogService"); - -/** - * Backbone for IRC bridges running on vector.im through scalar - */ -class VectorIrcBackbone extends StubbedIrcBackbone { - - /** - * Creates a new Vector IRC backbone - * @param {string} roomId the room ID to manage - * @param {string} upstreamScalarToken the vector scalar token - */ - constructor(roomId, upstreamScalarToken) { - super(); - this._roomId = roomId; - this._scalarToken = upstreamScalarToken; - this._lastNetworkResponse = null; - } - - /*override*/ - getNetworks() { - return this._getNetworks().then(networks => _.map(networks, n => { - return {name: n.title, id: n.id}; - })); - } - - /*override*/ - getLinkedChannels() { - var networks; - return this._getNetworks().then(n => { - networks = n; - return VectorScalarClient.getIrcLinks(this._roomId, this._scalarToken); - }).then(links => { - var container = {}; - - var ridToServerId = {}; - - for (var network of networks) { - ridToServerId[network.rid] = network.id; - container[network.id] = []; - } - - for (var link of links) { - var server = ridToServerId[link.rid]; - if (!server) { - log.error("VectorIrcBackbone", "Could not find network for RID " + link.rid); - throw new Error("Unexpected RID"); - } - - container[server].push(link.channel); - } - - return container; - }); - } - - /*override*/ - getChannelOps(network, channel) { - return this._getNetworks().then(networks => { - var networkServer = null; - var rid = null; - for (var n of networks) { - if (n.id === network) { - networkServer = n.domain; - rid = n.rid; - break; - } - } - - return VectorScalarClient.getIrcOperators(rid, networkServer, '#' + channel, this._scalarToken); - }); - } - - /*override*/ - addChannel(network, channel, op) { - return this._getNetworks().then(networks => { - var networkServer = null; - var rid = null; - for (var n of networks) { - if (n.id === network) { - networkServer = n.domain; - rid = n.rid; - break; - } - } - - return VectorScalarClient.addIrcLink(rid, this._roomId, networkServer, '#' + channel, op, this._scalarToken); - }); - } - - /*override*/ - removeChannel(network, channel) { - return this._getNetworks().then(networks => { - var networkServer = null; - var rid = null; - for (var n of networks) { - if (n.id === network) { - networkServer = n.domain; - rid = n.rid; - break; - } - } - - return VectorScalarClient.removeIrcLink(rid, this._roomId, networkServer, '#' + channel, this._scalarToken); - }); - } - - _getNetworks() { - if (this._lastNetworkResponse !== null) return Promise.resolve(this._lastNetworkResponse); - return VectorScalarClient.getIrcNetworks(this._scalarToken).then(networks => { - this._lastNetworkResponse = networks; - return networks; - }); - } -} - -module.exports = VectorIrcBackbone; \ No newline at end of file diff --git a/src/integration/impl/rss/RSSBot.js b/src/integration/impl/rss/RSSBot.js deleted file mode 100644 index f3384b2..0000000 --- a/src/integration/impl/rss/RSSBot.js +++ /dev/null @@ -1,49 +0,0 @@ -var ComplexBot = require("../../generic_types/ComplexBot"); - -/** - * Represents an RSS bot - */ -class RSSBot extends ComplexBot { - - /** - * Creates a new RSS bot - * @param botConfig the bot configuration - * @param backbone the backbone powering this bot - */ - constructor(botConfig, backbone) { - super(botConfig); - this._backbone = backbone; - } - - /*override*/ - getUserId() { - return this._backbone.getUserId(); - } - - /*override*/ - getState() { - var response = { - feeds: [], - immutableFeeds: [] - }; - return this._backbone.getFeeds().then(feeds => { - response.feeds = feeds; - return this._backbone.getImmutableFeeds(); - }).then(feeds => { - response.immutableFeeds = feeds; - return response; - }); - } - - /*override*/ - removeFromRoom(roomId) { - return this._backbone.removeFromRoom(roomId); - } - - /*override*/ - updateState(newState) { - return this._backbone.setFeeds(newState.feeds).then(() => this.getState()); - } -} - -module.exports = RSSBot; \ No newline at end of file diff --git a/src/integration/impl/rss/RSSFactory.js b/src/integration/impl/rss/RSSFactory.js deleted file mode 100644 index f800a7b..0000000 --- a/src/integration/impl/rss/RSSFactory.js +++ /dev/null @@ -1,20 +0,0 @@ -var RSSBot = require("./RSSBot"); -var VectorRssBackbone = require("./VectorRssBackbone"); -var UpstreamConfiguration = require("../../../UpstreamConfiguration"); - -var factory = (db, integrationConfig, roomId, scalarToken) => { - factory.validateConfig(integrationConfig); - - return db.getUpstreamToken(scalarToken).then(upstreamToken => { - var backbone = new VectorRssBackbone(roomId, upstreamToken); - return new RSSBot(integrationConfig, backbone); - }); -}; - -factory.validateConfig = (integrationConfig) => { - if (!integrationConfig.upstream) throw new Error("Unsupported configuration"); - if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream"); - if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified"); -}; - -module.exports = factory; \ No newline at end of file diff --git a/src/integration/impl/rss/StubbedRssBackbone.js b/src/integration/impl/rss/StubbedRssBackbone.js deleted file mode 100644 index 6e51205..0000000 --- a/src/integration/impl/rss/StubbedRssBackbone.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Stubbed/placeholder RSS backbone - */ -class StubbedRssBackbone { - - /** - * Creates a new stubbed RSS backbone - */ - constructor() { - } - - /** - * Gets the user ID for this backbone - * @returns {Promise} resolves to the user ID - */ - getUserId() { - throw new Error("Not implemented"); - } - - /** - * Gets the feeds for this backbone - * @returns {Promise} resolves to the collection of feeds - */ - getFeeds() { - throw new Error("Not implemented"); - } - - /** - * Sets the new feeds for this backbone - * @param {string[]} newFeeds the new feed URLs - * @returns {Promise<>} resolves when complete - */ - setFeeds(newFeeds) { - throw new Error("Not implemented"); - } - - /** - * Gets the immutable feeds for this backbone - * @returns {Promise<{url:string,ownerId:string}>} resolves to the collection of immutable feeds - */ - getImmutableFeeds() { - throw new Error("Not implemented"); - } - - /** - * Removes the bot from the given room - * @param {string} roomId the room ID to remove the bot from - * @returns {Promise<>} resolves when completed - */ - removeFromRoom(roomId) { - throw new Error("Not implemented"); - } -} - -module.exports = StubbedRssBackbone; \ No newline at end of file diff --git a/src/integration/impl/rss/VectorRssBackbone.js b/src/integration/impl/rss/VectorRssBackbone.js deleted file mode 100644 index d4a37f6..0000000 --- a/src/integration/impl/rss/VectorRssBackbone.js +++ /dev/null @@ -1,82 +0,0 @@ -var StubbedRssBackbone = require("./StubbedRssBackbone"); -var VectorScalarClient = require("../../../scalar/VectorScalarClient"); -var _ = require("lodash"); -var log = require("../../../util/LogService"); - -/** - * Backbone for RSS bots running on vector.im through scalar - */ -class VectorRssBackbone extends StubbedRssBackbone { - - /** - * Creates a new Vector RSS backbone - * @param {string} roomId the room ID to manage - * @param {string} upstreamScalarToken the vector scalar token - */ - constructor(roomId, upstreamScalarToken) { - super(); - this._roomId = roomId; - this._scalarToken = upstreamScalarToken; - this._info = null; - this._otherFeeds = []; - } - - /*override*/ - getUserId() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - return this._info.bot_user_id; - }); - } - - /*override*/ - getFeeds() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - if (this._info.integrations.length == 0) return []; - return _.keys(this._info.integrations[0].config.feeds); - }); - } - - /*override*/ - setFeeds(newFeeds) { - var feedConfig = {}; - for (var feed of newFeeds) feedConfig[feed] = {}; - - return VectorScalarClient.configureIntegration("rssbot", this._scalarToken, { - feeds: feedConfig, - room_id: this._roomId - }); - } - - /*override*/ - getImmutableFeeds() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - return this._otherFeeds; - }); - } - - _getInfo() { - return VectorScalarClient.getIntegrationsForRoom(this._roomId, this._scalarToken).then(integrations => { - this._otherFeeds = []; - for (var integration of integrations) { - if (integration.self) continue; // skip - we're not looking for ones we know about - if (integration.type == "rssbot") { - var urls = _.keys(integration.config.feeds); - for (var url of urls) { - this._otherFeeds.push({url: url, ownerId: integration.user_id}); - } - } - } - - return VectorScalarClient.getIntegration("rssbot", this._roomId, this._scalarToken); - }).then(info => { - this._info = info; - }); - } - - /*override*/ - removeFromRoom(roomId) { - return VectorScalarClient.removeIntegration("rssbot", roomId, this._scalarToken); - } -} - -module.exports = VectorRssBackbone; \ No newline at end of file diff --git a/src/integration/impl/simple_bot/HostedSimpleBackbone.js b/src/integration/impl/simple_bot/HostedSimpleBackbone.js deleted file mode 100644 index ce10929..0000000 --- a/src/integration/impl/simple_bot/HostedSimpleBackbone.js +++ /dev/null @@ -1,31 +0,0 @@ -var sdk = require("matrix-js-sdk"); -var log = require("../../../util/LogService"); -var StubbedSimpleBackbone = require("./StubbedSimpleBackbone"); - -/** - * Standalone (matrix) backbone for simple bots - */ -class HostedSimpleBackbone extends StubbedSimpleBackbone { - - /** - * Creates a new standalone bot backbone - * @param {*} botConfig the configuration for the bot - */ - constructor(botConfig) { - super(botConfig); - this._config = botConfig; - this._client = sdk.createClient({ - baseUrl: this._config.hosted.homeserverUrl, - accessToken: this._config.hosted.accessToken, - userId: this._config.userId, - }); - } - - /*override*/ - removeFromRoom(roomId) { - log.info("HostedSimpleBackbone", "Removing " + this._config.userId + " from " + roomId); - return this._client.leave(roomId); - } -} - -module.exports = HostedSimpleBackbone; \ No newline at end of file diff --git a/src/integration/impl/simple_bot/SimpleBot.js b/src/integration/impl/simple_bot/SimpleBot.js deleted file mode 100644 index 08cffbd..0000000 --- a/src/integration/impl/simple_bot/SimpleBot.js +++ /dev/null @@ -1,24 +0,0 @@ -var IntegrationStub = require("../../generic_types/IntegrationStub"); - -/** - * Represents an RSS bot - */ -class SimpleBot extends IntegrationStub { - - /** - * Creates a new RSS bot - * @param botConfig the bot configuration - * @param backbone the backbone powering this bot - */ - constructor(botConfig, backbone) { - super(botConfig); - this._backbone = backbone; - } - - /*override*/ - removeFromRoom(roomId) { - return this._backbone.removeFromRoom(roomId); - } -} - -module.exports = SimpleBot; \ No newline at end of file diff --git a/src/integration/impl/simple_bot/SimpleBotFactory.js b/src/integration/impl/simple_bot/SimpleBotFactory.js deleted file mode 100644 index 1c4c1cc..0000000 --- a/src/integration/impl/simple_bot/SimpleBotFactory.js +++ /dev/null @@ -1,27 +0,0 @@ -var SimpleBot = require("./SimpleBot"); -var VectorSimpleBackbone = require("./VectorSimpleBackbone"); -var HostedSimpleBackbone = require("./HostedSimpleBackbone"); -var UpstreamConfiguration = require("../../../UpstreamConfiguration"); - -var factory = (db, integrationConfig, roomId, scalarToken) => { - factory.validateConfig(integrationConfig); - - if (integrationConfig.upstream) { - return db.getUpstreamToken(scalarToken).then(upstreamToken => { - var backbone = new VectorSimpleBackbone(integrationConfig, upstreamToken); - return new SimpleBot(integrationConfig, backbone); - }); - } else if (integrationConfig.hosted) { - var backbone = new HostedSimpleBackbone(integrationConfig); - return Promise.resolve(new SimpleBot(integrationConfig, backbone)); - } -}; - -factory.validateConfig = (integrationConfig) => { - if (integrationConfig.upstream) { - if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream"); - if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified"); - } else if (!integrationConfig.hosted) throw new Error("Unsupported configuration"); -}; - -module.exports = factory; \ No newline at end of file diff --git a/src/integration/impl/simple_bot/StubbedSimpleBackbone.js b/src/integration/impl/simple_bot/StubbedSimpleBackbone.js deleted file mode 100644 index a864912..0000000 --- a/src/integration/impl/simple_bot/StubbedSimpleBackbone.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Stubbed backbone for simple bots - */ -class StubbedSimpleBackbone { - - /** - * Creates a new stubbed bot backbone - * @param {*} botConfig the configuration for the bot - */ - constructor(botConfig) { - this._config = botConfig; - } - - /** - * Leaves a given Matrix room - * @param {string} roomId the room to leave - * @returns {Promise<>} resolves when completed - */ - removeFromRoom(roomId) { - throw new Error("Not implemented"); - } -} - -module.exports = StubbedSimpleBackbone; \ No newline at end of file diff --git a/src/integration/impl/simple_bot/VectorSimpleBackbone.js b/src/integration/impl/simple_bot/VectorSimpleBackbone.js deleted file mode 100644 index 2d8fcfc..0000000 --- a/src/integration/impl/simple_bot/VectorSimpleBackbone.js +++ /dev/null @@ -1,28 +0,0 @@ -var VectorScalarClient = require("../../../scalar/VectorScalarClient"); -var log = require("../../../util/LogService"); -var StubbedSimpleBackbone = require("./StubbedSimpleBackbone"); - -/** - * Vector backbone for simple bots - */ -class VectorSimpleBackbone extends StubbedSimpleBackbone { - - /** - * Creates a new vector bot backbone - * @param {*} botConfig the configuration for the bot - * @param {string} upstreamScalarToken the upstream scalar token - */ - constructor(botConfig, upstreamScalarToken) { - super(botConfig); - this._config = botConfig; - this._upstreamToken = upstreamScalarToken; - } - - /*override*/ - removeFromRoom(roomId) { - log.info("VectorSimpleBackbone", "Removing " + this._config.userId + " from " + roomId); - return VectorScalarClient.removeIntegration(this._config.upstream.id, roomId, this._upstreamToken); - } -} - -module.exports = VectorSimpleBackbone; \ No newline at end of file diff --git a/src/integration/impl/simple_widget/SimpleWidget.js b/src/integration/impl/simple_widget/SimpleWidget.js deleted file mode 100644 index b916d6b..0000000 --- a/src/integration/impl/simple_widget/SimpleWidget.js +++ /dev/null @@ -1,17 +0,0 @@ -var Widget = require("../../generic_types/Widget"); - -/** - * Represents a simple widget - */ -class SimpleWidget extends Widget { - - /** - * Creates a new simple widget - * @param widgetConfig the widget configuration - */ - constructor(widgetConfig) { - super(widgetConfig); - } -} - -module.exports = SimpleWidget; \ No newline at end of file diff --git a/src/integration/impl/simple_widget/SimpleWidgetFactory.js b/src/integration/impl/simple_widget/SimpleWidgetFactory.js deleted file mode 100644 index 34782e0..0000000 --- a/src/integration/impl/simple_widget/SimpleWidgetFactory.js +++ /dev/null @@ -1,13 +0,0 @@ -var SimpleWidget = require("./SimpleWidget"); -var Promise = require("bluebird"); - -var factory = (db, integrationConfig, roomId, scalarToken) => { - factory.validateConfig(integrationConfig); - return Promise.resolve(new SimpleWidget(integrationConfig, roomId)); -}; - -factory.validateConfig = (integrationConfig) => { - // Nothing to do -}; - -module.exports = factory; \ No newline at end of file diff --git a/src/integration/impl/travisci/StubbedTravisCiBackbone.js b/src/integration/impl/travisci/StubbedTravisCiBackbone.js deleted file mode 100644 index 58b60c1..0000000 --- a/src/integration/impl/travisci/StubbedTravisCiBackbone.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Stubbed/placeholder Travis CI backbone - */ -class StubbedTravisCiBackbone { - - /** - * Creates a new stubbed RSS backbone - */ - constructor() { - } - - /** - * Gets the user ID for this backbone - * @returns {Promise} resolves to the user ID - */ - getUserId() { - throw new Error("Not implemented"); - } - - /** - * Gets the repository templates for this backbone - * @returns {Promise<{repoKey:string,template:string}[]>} resolves to the collection of repositories and their templates - */ - getRepos() { - throw new Error("Not implemented"); - } - - /** - * Gets the immutable repository templates for this backbone (set by other users) - * @returns {Promise<{repoKey:string,template:string,ownerId:string}[]>} resolves to the collection of repositories and their templates - */ - getImmutableRepos() { - throw new Error("Not implemented"); - } - - /** - * Sets the new repository templates for this backbone - * @param {{repoKey:string,template:string}[]} newRepos the new templates for the repositories - * @returns {Promise<>} resolves when complete - */ - setRepos(newRepos) { - throw new Error("Not implemented"); - } - - /** - * Gets the webhook url for this backbone - * @returns {Promise} resolves to the webhook URL - */ - getWebhookUrl() { - throw new Error("Not implemented"); - } - - /** - * Removes the bot from the given room - * @param {string} roomId the room ID to remove the bot from - * @returns {Promise<>} resolves when completed - */ - removeFromRoom(roomId) { - throw new Error("Not implemented"); - } -} - -module.exports = StubbedTravisCiBackbone; \ No newline at end of file diff --git a/src/integration/impl/travisci/TravisCiBot.js b/src/integration/impl/travisci/TravisCiBot.js deleted file mode 100644 index dbedbed..0000000 --- a/src/integration/impl/travisci/TravisCiBot.js +++ /dev/null @@ -1,53 +0,0 @@ -var ComplexBot = require("../../generic_types/ComplexBot"); - -/** - * Represents a Travis CI bot - */ -class TravisCiBot extends ComplexBot { - - /** - * Creates a new Travis CI bot - * @param botConfig the bot configuration - * @param backbone the backbone powering this bot - */ - constructor(botConfig, backbone) { - super(botConfig); - this._backbone = backbone; - } - - /*override*/ - getUserId() { - return this._backbone.getUserId(); - } - - /*override*/ - getState() { - var response = { - repoTemplates: [], - immutableRepoTemplates: [], - webhookUrl: "" - }; - return this._backbone.getRepos().then(templates => { - response.repoTemplates = templates; - return this._backbone.getImmutableRepos(); - }).then(immutable => { - response.immutableRepoTemplates = immutable; - return this._backbone.getWebhookUrl(); - }).then(url => { - response.webhookUrl = url; - return response; - }); - } - - /*override*/ - removeFromRoom(roomId) { - return this._backbone.removeFromRoom(roomId); - } - - /*override*/ - updateState(newState) { - return this._backbone.setRepos(newState.repoTemplates).then(() => this.getState()); - } -} - -module.exports = TravisCiBot; \ No newline at end of file diff --git a/src/integration/impl/travisci/TravisCiFactory.js b/src/integration/impl/travisci/TravisCiFactory.js deleted file mode 100644 index 42ac8e9..0000000 --- a/src/integration/impl/travisci/TravisCiFactory.js +++ /dev/null @@ -1,20 +0,0 @@ -var TravisCiBot = require("./TravisCiBot"); -var VectorTravisCiBackbone = require("./VectorTravisCiBackbone"); -var UpstreamConfiguration = require("../../../UpstreamConfiguration"); - -var factory = (db, integrationConfig, roomId, scalarToken) => { - factory.validateConfig(integrationConfig); - - return db.getUpstreamToken(scalarToken).then(upstreamToken => { - var backbone = new VectorTravisCiBackbone(roomId, upstreamToken); - return new TravisCiBot(integrationConfig, backbone); - }); -}; - -factory.validateConfig = (integrationConfig) => { - if (!integrationConfig.upstream) throw new Error("Unsupported configuration"); - if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream"); - if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified"); -}; - -module.exports = factory; \ No newline at end of file diff --git a/src/integration/impl/travisci/VectorTravisCiBackbone.js b/src/integration/impl/travisci/VectorTravisCiBackbone.js deleted file mode 100644 index 3d99d78..0000000 --- a/src/integration/impl/travisci/VectorTravisCiBackbone.js +++ /dev/null @@ -1,108 +0,0 @@ -var StubbedTravisCiBackbone = require("./StubbedTravisCiBackbone"); -var VectorScalarClient = require("../../../scalar/VectorScalarClient"); -var _ = require("lodash"); -var log = require("../../../util/LogService"); - -/** - * Backbone for Travis CI bots running on vector.im through scalar - */ -class VectorTravisCiBackbone extends StubbedTravisCiBackbone { - - /** - * Creates a new Vector Travis CI backbone - * @param {string} roomId the room ID to manage - * @param {string} upstreamScalarToken the vector scalar token - */ - constructor(roomId, upstreamScalarToken) { - super(); - this._roomId = roomId; - this._scalarToken = upstreamScalarToken; - this._info = null; - this._otherTemplates = []; - } - - /*override*/ - getUserId() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - return this._info.bot_user_id; - }); - } - - /*override*/ - getRepos() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - if (this._info.integrations.length == 0) return []; - - var rooms = _.keys(this._info.integrations[0].config.rooms); - if (rooms.indexOf(this._roomId) === -1) return []; - - var repos = _.keys(this._info.integrations[0].config.rooms[this._roomId].repos); - return _.map(repos, r => { - return {repoKey: r, template: this._info.integrations[0].config.rooms[this._roomId].repos[r].template}; - }); - }); - } - - /*override*/ - getImmutableRepos() { - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - return this._otherTemplates; - }); - } - - /*override*/ - setRepos(newRepos) { - var config = {}; - config[this._roomId] = {repos: {}}; - for (var repo of newRepos) config[this._roomId].repos[repo.repoKey] = {template: repo.template}; - - return VectorScalarClient.configureIntegration("travis-ci", this._scalarToken, { - rooms: config - }); - } - - /*override*/ - getWebhookUrl() { - // string - return (this._info ? Promise.resolve() : this._getInfo()).then(() => { - if (this._info.integrations.length == 0) return ""; - return this._info.integrations[0].config.webhook_url; - }); - } - - _getInfo() { - return VectorScalarClient.getIntegrationsForRoom(this._roomId, this._scalarToken).then(integrations => { - this._otherTemplates = []; - for (var integration of integrations) { - if (integration.self) continue; // skip - we're not looking for ones we know about - if (integration.type == "travis-ci") { - var roomIds = _.keys(integration.config.rooms); - if (roomIds.length === 0) continue; - if (roomIds.length !== 1) log.warn("VectorTravisCiBackbone", "Expected 1 room but found " + roomIds.length); - - var roomConfig = integration.config.rooms[roomIds[0]]; - var repositories = _.keys(roomConfig.repos); - - for (var repo of repositories) { - this._otherTemplates.push({ - repoKey: repo, - template: roomConfig.repos[repo].template, - ownerId: integration.user_id - }); - } - } - } - - return VectorScalarClient.getIntegration("travis-ci", this._roomId, this._scalarToken); - }).then(info => { - this._info = info; - }); - } - - /*override*/ - removeFromRoom(roomId) { - return VectorScalarClient.removeIntegration("travis-ci", roomId, this._scalarToken); - } -} - -module.exports = VectorTravisCiBackbone; \ No newline at end of file diff --git a/src/integration/index.js b/src/integration/index.js deleted file mode 100644 index dfa6a1d..0000000 --- a/src/integration/index.js +++ /dev/null @@ -1,78 +0,0 @@ -var config = require("config"); -var log = require("../util/LogService"); -var fs = require("fs"); -var path = require("path"); -var _ = require("lodash"); -var IntegrationImpl = require("./impl"); - -log.info("Integrations", "Discovering integrations"); - -var searchPath = path.join(process.cwd(), "config", "integrations"); -var files = _.filter(fs.readdirSync(searchPath), f => !fs.statSync(path.join(searchPath, f)).isDirectory() && f.endsWith(".yaml")); -var currentEnv = config.util.initParam("NODE_ENV", "development"); - -if (currentEnv !== "development" && currentEnv !== "production") - throw new Error("Unknown node environment: " + currentEnv); - -var configs = {}; - -for (var file of files) { - if (file.endsWith("_development.yaml") || file.endsWith("_production.yaml")) { - if (!file.endsWith("_" + currentEnv + ".yaml")) continue; - var fileName = file.replace("_development.yaml", "").replace("_production.yaml", "") + ".yaml"; - - if (!configs[fileName]) configs[fileName] = {}; - configs[fileName]["alt"] = config.util.parseFile(path.join(searchPath, file)) || {}; - } else { - if (!configs[file]) configs[file] = {}; - configs[file]["defaults"] = config.util.parseFile(path.join(searchPath, file)) || {}; - } -} - -var keys = _.keys(configs); -log.info("Integrations", "Discovered " + keys.length + " integrations. Parsing definitions..."); - -var linear = []; -var byUserId = {}; -var byType = {}; - -for (var key of keys) { - log.info("Integrations", "Preparing " + key); - if (!configs[key].defaults) configs[key].defaults = {}; - var merged = config.util.extendDeep(configs[key].defaults, configs[key].alt); - if (!merged['enabled']) { - log.warn("Integrations", "Integration " + key + " is not enabled - skipping"); - continue; - } - - var factory = IntegrationImpl.getFactory(merged); - if (!factory) { - log.warn("Integrations", "Integration " + key + " does not have an associated factory - skipping"); - continue; - } - try { - factory.validateConfig(merged); - } catch (err) { - log.error("Integrations", "Error while validating integration " + key + " - skipping"); - log.error("Integrations", err); - continue; - } - - linear.push(merged); - if (merged['userId']) - byUserId[merged['userId']] = merged; - - if (!byType[merged['type']]) - byType[merged['type']] = {}; - if (byType[merged['type']][merged['integrationType']]) - throw new Error("Duplicate type " + merged['type'] + " (" + merged['integrationType'] + ") at key " + key); - byType[merged['type']][merged['integrationType']] = merged; -} - -log.info("Integrations", "Loaded " + linear.length + " integrations"); - -module.exports = { - all: linear, - byUserId: byUserId, - byType: byType -}; \ No newline at end of file diff --git a/src/integrations/Bridge.ts b/src/integrations/Bridge.ts new file mode 100644 index 0000000..fef2643 --- /dev/null +++ b/src/integrations/Bridge.ts @@ -0,0 +1,23 @@ +import { Integration } from "./Integration"; +import BridgeRecord from "../db/models/BridgeRecord"; +import { AvailableNetworks, LinkedChannels } from "../bridges/IrcBridge"; + +export class Bridge extends Integration { + constructor(bridge: BridgeRecord, public config: any) { + super(bridge); + this.category = "bridge"; + this.requirements = [{ + condition: "publicRoom", + expectedValue: true, + argument: null, // not used + }]; + + // We'll just say we don't support encryption + this.isEncryptionSupported = false; + } +} + +export interface IrcBridgeConfiguration { + availableNetworks: AvailableNetworks; + links: LinkedChannels; +} \ No newline at end of file diff --git a/src/integrations/ComplexBot.ts b/src/integrations/ComplexBot.ts new file mode 100644 index 0000000..27d1792 --- /dev/null +++ b/src/integrations/ComplexBot.ts @@ -0,0 +1,31 @@ +import { Integration } from "./Integration"; +import NebIntegration from "../db/models/NebIntegration"; + +export class ComplexBot extends Integration { + constructor(bot: NebIntegration, public notificationUserId: string, public botUserId: string, public config: any) { + super(bot); + this.category = "complex-bot"; + this.requirements = []; + + // Notification bots are technically supported in e2e rooms + this.isEncryptionSupported = true; + } +} + +export interface RssBotConfiguration { + feeds: { + [url: string]: { + addedByUserId: string; + }; + }; +} + +export interface TravisCiConfiguration { + webhookId: string; + repos: { + [repoKey: string]: { + addedByUserId: string; + template: string; + } + } +} \ No newline at end of file diff --git a/src/integrations/Integration.ts b/src/integrations/Integration.ts new file mode 100644 index 0000000..c30a071 --- /dev/null +++ b/src/integrations/Integration.ts @@ -0,0 +1,35 @@ +import { IntegrationRecord } from "../db/models/IntegrationRecord"; + +export class Integration { + // These are meant to be set by the underlying integration + public category: "bot" | "complex-bot" | "bridge" | "widget"; + public type: string; + public requirements: IntegrationRequirement[]; + public isEncryptionSupported = false; + + // These are meant to be set by us + public displayName: string; + public avatarUrl: string; + public description: string; + public isEnabled: boolean; + public isPublic: boolean; + + constructor(record: IntegrationRecord) { + this.type = record.type; + this.displayName = record.name; + this.avatarUrl = record.avatarUrl; + this.description = record.description; + this.isEnabled = record.isEnabled; + this.isPublic = record.isPublic; + } +} + +export interface IntegrationRequirement { + condition: "publicRoom" | "canSendEventTypes" | "userInRoom"; + argument: any; + + // For publicRoom this is true or false (boolean) + // For canSendEventTypes this is an array of {isState: boolean, type: string} + // For userInRoom this is the user ID + expectedValue: any; +} \ No newline at end of file diff --git a/src/integrations/SimpleBot.ts b/src/integrations/SimpleBot.ts new file mode 100644 index 0000000..1e3b0e2 --- /dev/null +++ b/src/integrations/SimpleBot.ts @@ -0,0 +1,13 @@ +import { Integration } from "./Integration"; +import NebIntegration from "../db/models/NebIntegration"; + +export class SimpleBot extends Integration { + constructor(bot: NebIntegration, public userId: string) { + super(bot); + this.category = "bot"; + this.requirements = []; + + // We're going to go ahead and claim that none of the bots are supported in e2e rooms + this.isEncryptionSupported = false; + } +} \ No newline at end of file diff --git a/src/integrations/Widget.ts b/src/integrations/Widget.ts new file mode 100644 index 0000000..536d3b8 --- /dev/null +++ b/src/integrations/Widget.ts @@ -0,0 +1,29 @@ +import { Integration } from "./Integration"; +import WidgetRecord from "../db/models/WidgetRecord"; + +export interface EtherpadWidgetOptions { + defaultUrl: string; +} + +export interface JitsiWidgetOptions { + jitsiDomain: string; + scriptUrl: string; +} + +export class Widget extends Integration { + public options: any; + + constructor(widgetRecord: WidgetRecord) { + super(widgetRecord); + this.category = "widget"; + this.options = widgetRecord.optionsJson ? JSON.parse(widgetRecord.optionsJson) : {}; + this.requirements = [{ + condition: "canSendEventTypes", + argument: [{isState: true, type: "im.vector.widget"}], + expectedValue: true, + }]; + + // Technically widgets are supported in encrypted rooms, although at risk. + this.isEncryptionSupported = true; + } +} \ No newline at end of file diff --git a/src/matrix/DemoBot.js b/src/matrix/DemoBot.js deleted file mode 100644 index 1b519a9..0000000 --- a/src/matrix/DemoBot.js +++ /dev/null @@ -1,66 +0,0 @@ -var sdk = require("matrix-js-sdk"); -var log = require("../util/LogService"); - -/** - * Dimension demo bot. Doesn't do anything except show how to add a self-hosted bot to Dimension - */ -class DemoBot { - - constructor(homeserverUrl, userId, accessToken) { - this._rooms = []; - - log.info("DemoBot", "Constructing bot as " + userId); - this._client = sdk.createClient({ - baseUrl: homeserverUrl, - accessToken: accessToken, - userId: userId, - }); - - this._client.on('event', event => { - if (event.getType() !== "m.room.member") return; - if (event.getStateKey() != this._client.credentials.userId) return; - if (event.getContent().membership === 'invite') { - if (this._rooms.indexOf(event.getRoomId()) !== -1) return; - log.info("DemoBot", "Joining " + event.getRoomId()); - this._client.joinRoom(event.getRoomId()).then(() => { - this._recalculateRooms(); - this._client.sendMessage(event.getRoomId(), { - msgtype: "m.notice", - body: "Hello! I'm a small bot that acts as an example for how to set up your own bot on Dimension." - }); - }); - } else this._recalculateRooms(); - }); - } - - start() { - log.info("DemoBot", "Starting bot"); - this._client.startClient(); - - this._client.on('sync', state => { - if (state == 'PREPARED') this._recalculateRooms(); - }); - } - - _recalculateRooms() { - var rooms = this._client.getRooms(); - this._rooms = []; - for (var room of rooms) { - var me = room.getMember(this._client.credentials.userId); - if (!me) continue; - - if (me.membership == "invite") { - this._client.joinRoom(room.roomId); - continue; - } - - if (me.membership != "join") continue; - this._rooms.push(room.roomId); - } - - log.verbose("DemoBot", "Currently in " + this._rooms.length + " rooms"); - } - -} - -module.exports = DemoBot; \ No newline at end of file diff --git a/src/matrix/MatrixAppserviceClient.ts b/src/matrix/MatrixAppserviceClient.ts new file mode 100644 index 0000000..996c9ec --- /dev/null +++ b/src/matrix/MatrixAppserviceClient.ts @@ -0,0 +1,42 @@ +import { doClientApiCall } from "./helpers"; +import AppService from "../db/models/AppService"; + +export interface MatrixUserResponse { + access_token: string; + device_id: string; + home_server: string; + user_id: string; +} + +export class MatrixAppserviceClient { + + constructor(private appservice: AppService) { + } + + public async registerUser(localpart: string): Promise { + return doClientApiCall( + "POST", + "/_matrix/client/r0/register", + {access_token: this.appservice.asToken}, + {type: "m.login.application_service", username: localpart}, + ); + } + + public async whoAmI(): Promise { + const response = await doClientApiCall( + "GET", + "/_matrix/client/r0/account/whoami", + {access_token: this.appservice.asToken}, + ); + return response['user_id']; + } + + public async leaveRoom(virtualUserId: string, roomId: string): Promise { + return doClientApiCall( + "POST", + "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/leave", + {access_token: this.appservice.asToken, user_id: virtualUserId}, + {}, + ); + } +} diff --git a/src/matrix/MatrixLiteClient.js b/src/matrix/MatrixLiteClient.js deleted file mode 100644 index 0cd9151..0000000 --- a/src/matrix/MatrixLiteClient.js +++ /dev/null @@ -1,83 +0,0 @@ -var request = require('request'); -var log = require("../util/LogService"); -var dns = require("dns-then"); -var Promise = require("bluebird"); -var config = require("config"); - -/** - * Represents a lightweight matrix client with minimal functionality - */ -class MatrixLiteClient { - - /** - * Creates a new matrix client - * @param {OpenID} openId the open ID to use - */ - constructor(openId) { - this._openId = openId; - } - - /** - * Gets the Matrix User ID that owns this open ID - * @return {Promise} resolves to the mxid - */ - getSelfMxid() { - return this._do("GET", "/_matrix/federation/v1/openid/userinfo", /*qs=*/null, /*body=*/null, /*allowSelfSigned=*/true).then((response, body) => { - var json = JSON.parse(response.body); - return json['sub']; - }); - } - - /** - * Gets a URL preview from the media repo (as provided by the default homeserver) - * @param {string} url The URL to get the preview of - * @return {Promise<*>} resolves to the raw URL preview - */ - static getUrlPreview(url) { - return MatrixLiteClient._do(config.homeserver.name, config.homeserver.accessToken, "GET", "/_matrix/media/r0/preview_url", {url: url}).then((response, body) => { - return JSON.parse(response.body); - }); - } - - _do(method, endpoint, qs = null, body = null, allowSelfSigned = false) { - return MatrixLiteClient._do(this._openId.matrix_server_name, this._openId.access_token, method, endpoint, qs, body, allowSelfSigned); - } - - static _do(serverName, accessToken, method, endpoint, qs = null, body = null, allowSelfSigned = false) { - // HACK: We have to wrap the dns promise in a Bluebird promise just to make sure it works - var dnsPromise = dns.resolveSrv("_matrix._tcp." + serverName); - return Promise.resolve(dnsPromise).then(records => { - if (records && records.length > 0) - serverName = records[0].name + ":" + records[0].port; - }, err => { - log.warn("MatrixLiteClient", "Failed to lookup SRV for " + serverName + " - assuming none available."); - log.warn("MatrixLiteClient", err); - }).then(() => { - var url = "https://" + serverName + endpoint; - - log.verbose("MatrixLiteClient", "Performing request: " + url); - - if (!qs) qs = {}; - if (accessToken) qs['access_token'] = accessToken; - - var params = { - url: url, - method: method, - form: body, - qs: qs, - rejectUnauthorized: !allowSelfSigned - }; - - return new Promise((resolve, reject) => { - request(params, (err, response, body) => { - if (err) { - log.error("MatrixLiteClient", err); - reject(err); - } else resolve(response, body); - }); - }); - }); - } -} - -module.exports = MatrixLiteClient; \ No newline at end of file diff --git a/src/matrix/MatrixLiteClient.ts b/src/matrix/MatrixLiteClient.ts new file mode 100644 index 0000000..9470914 --- /dev/null +++ b/src/matrix/MatrixLiteClient.ts @@ -0,0 +1,29 @@ +import { doClientApiCall } from "./helpers"; + +export interface MatrixUrlPreview { + // This is really the only parameter we care about + "og:title"?: string; +} + +export class MatrixLiteClient { + + constructor(private accessToken: string) { + } + + public async getUrlPreview(url: string): Promise { + return doClientApiCall( + "GET", + "/_matrix/media/r0/preview_url", + {access_token: this.accessToken, url: url} + ); + } + + public async whoAmI(): Promise { + const response = await doClientApiCall( + "GET", + "/_matrix/client/r0/account/whoami", + {access_token: this.accessToken} + ); + return response['user_id']; + } +} diff --git a/src/matrix/MatrixOpenIdClient.ts b/src/matrix/MatrixOpenIdClient.ts new file mode 100644 index 0000000..cae3d3e --- /dev/null +++ b/src/matrix/MatrixOpenIdClient.ts @@ -0,0 +1,20 @@ +import { doFederatedApiCall } from "./helpers"; +import { OpenId } from "../models/OpenId"; + +export class MatrixOpenIdClient { + + constructor(private openId: OpenId) { + } + + public async getUserId(): Promise { + // TODO: Implement/prefer https://github.com/matrix-org/matrix-doc/issues/1115 + // #1115 also means this should become a client API call, not a federated one (finally) + const response = await doFederatedApiCall( + "GET", + this.openId.matrix_server_name, + "/_matrix/federation/v1/openid/userinfo", + {access_token: this.openId.access_token} + ); + return response['sub']; + } +} diff --git a/src/matrix/helpers.ts b/src/matrix/helpers.ts new file mode 100644 index 0000000..38c9663 --- /dev/null +++ b/src/matrix/helpers.ts @@ -0,0 +1,100 @@ +import * as dns from "dns-then"; +import { LogService } from "matrix-js-snippets"; +import { Cache, CACHE_FEDERATION } from "../MemoryCache"; +import * as request from "request"; +import config from "../config"; + +export async function getFederationUrl(serverName: string): Promise { + const cachedUrl = Cache.for(CACHE_FEDERATION).get(serverName); + if (cachedUrl) { + LogService.verbose("matrix", "Cached federation URL for " + serverName + " is " + cachedUrl); + return cachedUrl; + } + + if (serverName === config.homeserver.name && config.homeserver.federationUrl) { + let url = config.homeserver.federationUrl; + if (url.endsWith("/")) { + url = url.substring(0, url.length - 1); + } + + LogService.info("matrix", "Using configured federation URL for " + serverName); + Cache.for(CACHE_FEDERATION).put(serverName, url); + return url; + } + + let serverUrl = null; + let expirationMs = 4 * 60 * 60 * 1000; // default is 4 hours + + try { + const records = await dns.resolveSrv("_matrix._tcp." + serverName); + if (records && records.length > 0) { + serverUrl = "https://" + records[0].name + ":" + records[0].port; + expirationMs = records[0].ttl * 1000; + } + } catch (err) { + // Not having the SRV record isn't bad, it just means that the server operator decided to not use SRV records. + // When there's no SRV record we default to port 8448 (as per the federation rules) in the lower .then() + // People tend to think that the lack of an SRV record is bad, but in reality it's only a problem if one was set and + // it's not being found. Most people don't set up the SRV record, but some do. + LogService.verbose("matrix", err); + LogService.warn("matrix", "Could not find _matrix._tcp." + serverName + " DNS record. This is normal for most servers."); + } + + if (!serverUrl) serverUrl = "https://" + serverName + ":8448"; + LogService.verbose("matrix", "Federation URL for " + serverName + " is " + serverUrl + " - caching for " + expirationMs + " ms"); + Cache.for(CACHE_FEDERATION).put(serverName, serverUrl, expirationMs); + return serverUrl; +} + +export async function doFederatedApiCall(method: string, serverName: string, endpoint: string, query?: object, body?: object): Promise { + const federationUrl = await getFederationUrl(serverName); + LogService.info("matrix", "Doing federated API call: " + federationUrl + endpoint); + return new Promise((resolve, reject) => { + request({ + method: method, + url: federationUrl + endpoint, + qs: query, + json: body, + rejectUnauthorized: false, // allow self signed certs (for federation) + }, (err, res, _body) => { + if (err) { + LogService.error("matrix", "Error calling " + endpoint); + LogService.error("matrix", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("matrix", "Got status code " + res.statusCode + " while calling federated endpoint " + endpoint); + reject(new Error("Error in request: invalid status code")); + } else { + if (typeof(res.body) === "string") res.body = JSON.parse(res.body); + resolve(res.body); + } + }); + }); +} + +export async function doClientApiCall(method: string, endpoint: string, query?: object, body?: object): Promise { + let url = config.homeserver.clientServerUrl; + if (url.endsWith("/")) url = url.substring(0, url.length - 1); + LogService.info("matrix", "Doing client API call: " + url + endpoint); + + return new Promise((resolve, reject) => { + request({ + method: method, + url: url + endpoint, + qs: query, + json: body, + }, (err, res, _body) => { + if (err) { + LogService.error("matrix", "Error calling " + endpoint); + LogService.error("matrix", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("matrix", "Got status code " + res.statusCode + " while calling client endpoint " + endpoint); + reject(new Error("Error in request: invalid status code")); + } else { + if (typeof(res.body) === "string") res.body = JSON.parse(res.body); + resolve(res.body); + } + }); + }); +} \ No newline at end of file diff --git a/src/models/MatrixEvent.ts b/src/models/MatrixEvent.ts new file mode 100644 index 0000000..d866c1f --- /dev/null +++ b/src/models/MatrixEvent.ts @@ -0,0 +1,12 @@ +export interface SimplifiedMatrixEvent { + age: number; + content: any; + event_id: string; + origin_server_ts: number; + room_id: string; + sender: string; + type: string; + unsigned: any; + + // Other keys would be stuff related to the actual event, like membership and state_key +} \ No newline at end of file diff --git a/src/models/ModularResponses.ts b/src/models/ModularResponses.ts new file mode 100644 index 0000000..f9bac1b --- /dev/null +++ b/src/models/ModularResponses.ts @@ -0,0 +1,11 @@ +export interface ModularIntegrationInfoResponse { + bot_user_id: string; + integrations?: any[]; +} + +export interface ModularIrcResponse { + replies: { + rid: string; + response: T; + }[]; +} \ No newline at end of file diff --git a/src/models/OpenId.ts b/src/models/OpenId.ts new file mode 100644 index 0000000..6f61827 --- /dev/null +++ b/src/models/OpenId.ts @@ -0,0 +1,6 @@ +export interface OpenId { + access_token: string; + matrix_server_name: string; + expires_in: number; + token_type: 'Bearer'; +} \ No newline at end of file diff --git a/src/models/ScalarResponses.ts b/src/models/ScalarResponses.ts new file mode 100644 index 0000000..a90dfa9 --- /dev/null +++ b/src/models/ScalarResponses.ts @@ -0,0 +1,8 @@ +export interface ScalarRegisterResponse { + scalar_token: string; +} + +export interface ScalarAccountResponse { + user_id: string; + // credit: number; // present on scalar-web +} \ No newline at end of file diff --git a/src/models/neb.ts b/src/models/neb.ts new file mode 100644 index 0000000..e2c2f74 --- /dev/null +++ b/src/models/neb.ts @@ -0,0 +1,21 @@ +import NebConfiguration from "../db/models/NebConfiguration"; +import { Integration } from "../integrations/Integration"; +import NebIntegration from "../db/models/NebIntegration"; + +export class NebConfig { + public id: number; + public adminUrl?: string; + public appserviceId?: string; + public upstreamId?: number; + public integrations: Integration[]; + public dbIntegrations: NebIntegration[]; + + public constructor(config: NebConfiguration, integrations: NebIntegration[]) { + this.id = config.id; + this.adminUrl = config.adminUrl; + this.appserviceId = config.appserviceId; + this.upstreamId = config.upstreamId; + this.integrations = integrations.map(i => new Integration(i)); + this.dbIntegrations = integrations; + } +} \ No newline at end of file diff --git a/src/neb/NebClient.ts b/src/neb/NebClient.ts new file mode 100644 index 0000000..3621141 --- /dev/null +++ b/src/neb/NebClient.ts @@ -0,0 +1,85 @@ +import { NebConfig } from "../models/neb"; +import { AppserviceStore } from "../db/AppserviceStore"; +import { Client } from "./models/client"; +import config from "../config"; +import { LogService } from "matrix-js-snippets"; +import { Service } from "./models/service"; +import * as request from "request"; + +export class NebClient { + constructor(private neb: NebConfig) { + } + + private async getAccessToken(userId: string): Promise { + const user = await AppserviceStore.getOrCreateUser(this.neb.appserviceId, userId); + return user.accessToken; + } + + public static getNebType(type: string): string { + if (type === "rss") return "rssbot"; + if (type === "travisci") return "travis-ci"; + + return type; + } + + public async updateUser(userId: string, isEnabled: boolean, sync = true, autoAcceptInvites = true): Promise { + const nebRequest: Client = { + UserID: userId, + HomeserverURL: config.homeserver.clientServerUrl, + AccessToken: (isEnabled ? await this.getAccessToken(userId) : "DISABLED"), + Sync: isEnabled ? sync : false, + AutoJoinRooms: autoAcceptInvites + }; + + return this.doRequest("/admin/configureClient", nebRequest); + } + + public async setServiceConfig(serviceId: string, userId: string, type: string, serviceConfig: any): Promise { + const nebRequest: Service = { + ID: serviceId, + Type: NebClient.getNebType(type), + UserID: userId, + Config: serviceConfig, + }; + + return this.doRequest("/admin/configureService", nebRequest); + } + + public async getServiceConfig(serviceId: string): Promise { + const nebRequest = {ID: serviceId}; + + try { + const service = await this.doRequest("/admin/getService", nebRequest); + return service.Config; + } catch (err) { + LogService.error("NebClient", err); + return {}; + } + } + + private doRequest(endpoint: string, body: any): Promise { + const adminUrl = (this.neb.adminUrl.endsWith("/") ? this.neb.adminUrl.substring(0, this.neb.adminUrl.length - 1) : this.neb.adminUrl); + if (!endpoint.startsWith("/")) endpoint = "/" + endpoint; + LogService.info("NebClient", "Doing NEB call: " + adminUrl + endpoint); + + return new Promise((resolve, reject) => { + request({ + method: "POST", + url: adminUrl + endpoint, + json: body, + }, (err, res, _body) => { + if (err) { + LogService.error("NebClient", "Error performing request"); + LogService.error("NebClient", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("NebClient", "Got status code " + res.statusCode + " while performing request"); + LogService.error("NebClient", res.body); + reject(new Error("Request error")); + } else { + resolve(res.body); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/neb/NebProxy.ts b/src/neb/NebProxy.ts new file mode 100644 index 0000000..7f87e8e --- /dev/null +++ b/src/neb/NebProxy.ts @@ -0,0 +1,357 @@ +import { NebConfig } from "../models/neb"; +import NebIntegration from "../db/models/NebIntegration"; +import { NebStore } from "../db/NebStore"; +import { LogService } from "matrix-js-snippets"; +import * as request from "request"; +import Upstream from "../db/models/Upstream"; +import UserScalarToken from "../db/models/UserScalarToken"; +import { NebClient } from "./NebClient"; +import { ModularIntegrationInfoResponse } from "../models/ModularResponses"; +import { AppserviceStore } from "../db/AppserviceStore"; +import { MatrixAppserviceClient } from "../matrix/MatrixAppserviceClient"; +import NebIntegrationConfig from "../db/models/NebIntegrationConfig"; +import { RssBotConfiguration, TravisCiConfiguration } from "../integrations/ComplexBot"; +import Webhook from "../db/models/Webhook"; +import * as randomString from "random-string"; + +interface InternalTravisCiConfig { + webhookUrl: string; + rooms: { + [roomId: string]: { + [repoKey: string]: { + template: string; + addedByUserId: string; + }; + }; + }; +} + +export class NebProxy { + constructor(private neb: NebConfig, private requestingUserId: string) { + } + + public async getBotUserId(integration: NebIntegration): Promise { + if (integration.nebId !== this.neb.id) throw new Error("Integration is not for this NEB proxy"); + + if (this.neb.upstreamId) { + try { + const response = await this.doUpstreamRequest("/integrations/" + NebClient.getNebType(integration.type)); + return response.bot_user_id; + } catch (err) { + LogService.error("NebProxy", err); + return null; + } + } else { + return (await NebStore.getOrCreateBotUser(this.neb.id, integration.type)).appserviceUserId; + } + } + + public async getNotificationUserId(integration: NebIntegration, inRoomId: string): Promise { + if (integration.nebId !== this.neb.id) throw new Error("Integration is not for this NEB proxy"); + + if (this.neb.upstreamId) { + try { + const response = await this.doUpstreamRequest("/integrations/" + NebClient.getNebType(integration.type), { + room_id: inRoomId, + }); + return response.bot_user_id; + } catch (err) { + LogService.error("NebProxy", err); + return null; + } + } else { + return (await NebStore.getOrCreateNotificationUser(this.neb.id, integration.type, this.requestingUserId)).appserviceUserId; + } + } + + public async getServiceConfiguration(integration: NebIntegration, inRoomId: string): Promise { + if (integration.nebId !== this.neb.id) throw new Error("Integration is not for this NEB proxy"); + + let result = null; + if (this.neb.upstreamId) { + try { + const response = await this.doUpstreamRequest("/integrations/" + NebClient.getNebType(integration.type), { + room_id: inRoomId, + }); + + if (integration.type === "rss") result = await this.parseUpstreamRssConfiguration(response.integrations); + else if (integration.type === "travisci") result = await this.parseUpstreamTravisCiConfiguration(response.integrations); + } catch (err) { + LogService.error("NebProxy", err); + } + } else { + const serviceConfig = await NebIntegrationConfig.findOne({ + where: { + integrationId: integration.id, + roomId: inRoomId, + }, + }); + result = serviceConfig ? JSON.parse(serviceConfig.jsonContent) : {}; + } + + if (!result) result = {}; + if (integration.type === "travisci") { + let repos = result.rooms ? result.rooms[inRoomId] : {}; + if (!repos) repos = {}; + + // Replace the entire result to better represent how this should be sent + result = { + webhookId: await this.getWebhookId("travisci"), + repos: repos, + }; + } + + return result; + } + + public async setServiceConfiguration(integration: NebIntegration, inRoomId: string, newConfig: any): Promise { + if (integration.nebId !== this.neb.id) throw new Error("Integration is not for this NEB proxy"); + + if (!this.neb.upstreamId) { + const serviceConfig = await NebIntegrationConfig.findOne({ + where: { + integrationId: integration.id, + roomId: inRoomId, + }, + }); + if (serviceConfig) { + serviceConfig.jsonContent = JSON.stringify(newConfig); + await serviceConfig.save(); + } else { + await NebIntegrationConfig.create({ + integrationId: integration.id, + roomId: inRoomId, + jsonContent: JSON.stringify(newConfig), + }); + } + } + + if (integration.type === "rss") await this.updateRssConfiguration(inRoomId, newConfig); + else if (integration.type === "travisci") await this.updateTravisCiConfiguration(inRoomId, newConfig); + else throw new Error("Cannot update go-neb: unrecognized type"); + } + + private parseUpstreamRssConfiguration(integrations: any[]): RssBotConfiguration { + if (!integrations) return {feeds: {}}; + + const result: RssBotConfiguration = {feeds: {}}; + for (const integration of integrations) { + const userId = integration.user_id; + const feeds = integration.config ? integration.config.feeds : {}; + if (!userId || !feeds) continue; + + const urls = Object.keys(feeds); + urls.forEach(u => result.feeds[u] = {addedByUserId: userId}); + } + + return result; + } + + private parseUpstreamTravisCiConfiguration(integrations: any[]): InternalTravisCiConfig { + if (!integrations) return {rooms: {}, webhookUrl: null}; + + const result: InternalTravisCiConfig = {rooms: {}, webhookUrl: null}; + for (const integration of integrations) { + if (!integration.user_id || !integration.config || !integration.config.rooms) continue; + + const userId = integration.user_id; + if (userId === this.requestingUserId && integration.config.webhook_url && !result.webhookUrl) + result.webhookUrl = integration.config.webhook_url; + + const roomIds = Object.keys(integration.config.rooms); + for (const roomId of roomIds) { + if (!result.rooms[roomId]) result.rooms[roomId] = {}; + + const repoKeys = Object.keys(integration.config.rooms[roomId].repos || {}); + for (const repoKey of repoKeys) { + result.rooms[roomId][repoKey] = { + template: integration.config.rooms[roomId].repos[repoKey].template, + addedByUserId: userId, + }; + } + } + } + + return result; + } + + private async updateRssConfiguration(roomId: string, newOpts: RssBotConfiguration): Promise { + const feedUrls = Object.keys(newOpts.feeds).filter(f => newOpts.feeds[f].addedByUserId === this.requestingUserId); + const newConfig = {feeds: {}}; + let currentConfig = {feeds: {}}; + + if (this.neb.upstreamId) { + const response = await this.doUpstreamRequest("/integrations/rssbot", {room_id: roomId}); + currentConfig = await this.parseUpstreamRssConfiguration(response.integrations); + } else { + const client = new NebClient(this.neb); + const notifUser = await NebStore.getOrCreateNotificationUser(this.neb.id, "rss", this.requestingUserId); + currentConfig = await client.getServiceConfig(notifUser.serviceId); + + if (feedUrls.length === 0) { + const appserviceClient = new MatrixAppserviceClient(await AppserviceStore.getAppservice(this.neb.appserviceId)); + await appserviceClient.leaveRoom(notifUser.appserviceUserId, roomId); + } + } + + if (!currentConfig || !currentConfig.feeds) currentConfig = {feeds: {}}; + + const allUrls = feedUrls.concat(Object.keys(currentConfig.feeds)); + for (const feedUrl of allUrls) { + let feed = currentConfig.feeds[feedUrl]; + if (!feed) feed = {poll_interval_mins: 60, rooms: []}; + + const hasRoom = feed.rooms.indexOf(roomId) !== -1; + const isEnabled = feedUrls.indexOf(feedUrl) !== -1; + + if (hasRoom && !isEnabled) { + feed.rooms.splice(feed.rooms.indexOf(roomId), 1); + } else if (!hasRoom && isEnabled) { + feed.rooms.push(roomId); + } + + if (feed.rooms.length > 0) { + newConfig.feeds[feedUrl] = { + poll_interval_mins: feed.poll_interval_mins, + rooms: feed.rooms, + }; + } + } + + if (this.neb.upstreamId) { + await this.doUpstreamRequest("/integrations/rssbot/configureService", { + room_id: roomId, + feeds: newConfig.feeds, + }); + } else { + const client = new NebClient(this.neb); + const notifUser = await NebStore.getOrCreateNotificationUser(this.neb.id, "rss", this.requestingUserId); + await client.setServiceConfig(notifUser.serviceId, notifUser.appserviceUserId, "rssbot", newConfig); + } + } + + private async updateTravisCiConfiguration(roomId: string, newOpts: TravisCiConfiguration): Promise { + const repoKeys = Object.keys(newOpts.repos).filter(f => newOpts.repos[f].addedByUserId === this.requestingUserId); + let newConfig = {rooms: {}}; + + if (!this.neb.upstreamId) { + const notifUser = await NebStore.getOrCreateNotificationUser(this.neb.id, "travisci", this.requestingUserId); + const client = new NebClient(this.neb); + newConfig = await client.getServiceConfig(notifUser.serviceId); // So we don't accidentally clear other rooms + + if (repoKeys.length === 0) { + const appserviceClient = new MatrixAppserviceClient(await AppserviceStore.getAppservice(this.neb.appserviceId)); + await appserviceClient.leaveRoom(notifUser.appserviceUserId, roomId); + } + } + + // Reset the current room's configuration so we don't keep artifacts. + newConfig.rooms[roomId] = {repos: {}}; + const roomReposConf = newConfig.rooms[roomId].repos; + + for (const repoKey of repoKeys) { + roomReposConf[repoKey] = { + template: newOpts.repos[repoKey].template, + }; + } + + if (this.neb.upstreamId) { + await this.doUpstreamRequest("/integrations/travis-ci/configureService", { + room_id: roomId, + rooms: newConfig.rooms, + }); + + // Annoyingly, we don't get any kind of feedback for the webhook - we have to re-request it + const response = await this.doUpstreamRequest("/integrations/travis-ci", { + room_id: roomId, + }); + const parsed = this.parseUpstreamTravisCiConfiguration(response.integrations); + if (parsed && parsed.webhookUrl) + await this.setWebhookTarget("travisci", parsed.webhookUrl); + } else { + const client = new NebClient(this.neb); + const notifUser = await NebStore.getOrCreateNotificationUser(this.neb.id, "travisci", this.requestingUserId); + const result = await client.setServiceConfig(notifUser.serviceId, notifUser.appserviceUserId, "travis-ci", newConfig); + if (result['NewConfig'] && result['NewConfig']['webhook_url']) + await this.setWebhookTarget("travisci", result['NewConfig']['webhook_url']); + } + } + + public async removeBotFromRoom(integration: NebIntegration, roomId: string): Promise { + if (integration.nebId !== this.neb.id) throw new Error("Integration is not for this NEB proxy"); + + if (this.neb.upstreamId) { + await this.doUpstreamRequest("/removeIntegration", {type: integration.type, room_id: roomId}); + } else { + const appservice = await AppserviceStore.getAppservice(this.neb.appserviceId); + const client = new MatrixAppserviceClient(appservice); + await client.leaveRoom(await this.getBotUserId(integration), roomId); + } + } + + private async getWebhookId(serviceId: string): Promise { + // We add a bit of uniqueness to the service ID to avoid conflicts + serviceId = serviceId + "_" + this.neb.id; + + let webhook = await Webhook.findOne({ + where: { + purposeId: serviceId, + ownerUserId: this.requestingUserId + } + }).catch(() => null); + if (!webhook) { + webhook = await Webhook.create({ + hookId: randomString({length: 160}), + ownerUserId: this.requestingUserId, + purposeId: serviceId, + targetUrl: null, // Will be populated later + }); + } + + return webhook.hookId; + } + + private async setWebhookTarget(serviceId: string, targetUrl: string): Promise { + const webhookId = await this.getWebhookId(serviceId); + const webhook = await Webhook.findByPrimary(webhookId); + webhook.targetUrl = targetUrl; + return webhook.save(); + } + + private async doUpstreamRequest(endpoint: string, body?: any): Promise { + const upstream = await Upstream.findByPrimary(this.neb.upstreamId); + const token = await UserScalarToken.findOne({ + where: { + upstreamId: upstream.id, + isDimensionToken: false, + userId: this.requestingUserId, + }, + }); + + const apiUrl = upstream.apiUrl.endsWith("/") ? upstream.apiUrl.substring(0, upstream.apiUrl.length - 1) : upstream.apiUrl; + const url = apiUrl + (endpoint.startsWith("/") ? endpoint : "/" + endpoint); + LogService.info("NebProxy", "Doing upstream NEB request: " + url); + + return new Promise((resolve, reject) => { + request({ + method: "POST", + url: url, + qs: {scalar_token: token.scalarToken}, + json: body, + }, (err, res, _body) => { + if (err) { + LogService.error("NebProxy", "Error calling" + url); + LogService.error("NebProxy", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("NebProxy", "Got status code " + res.statusCode + " when calling " + url); + LogService.error("NebProxy", res.body); + reject(new Error("Request failed")); + } else { + if (typeof(res.body) === "string") res.body = JSON.parse(res.body); + resolve(res.body); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/neb/models/client.ts b/src/neb/models/client.ts new file mode 100644 index 0000000..bf8218c --- /dev/null +++ b/src/neb/models/client.ts @@ -0,0 +1,13 @@ +export interface Client { + UserID: string; + HomeserverURL: string; + AccessToken: string; + Sync: boolean; + AutoJoinRooms?: boolean; + DisplayName?: string; +} + +export interface ConfigureClientResponse { + OldClient?: Client; + NewClient: Client; +} \ No newline at end of file diff --git a/src/neb/models/service.ts b/src/neb/models/service.ts new file mode 100644 index 0000000..1d17210 --- /dev/null +++ b/src/neb/models/service.ts @@ -0,0 +1,13 @@ +export interface Service { + ID: string; + UserID: string; + Type: string; + Config: any; +} + +export interface ConfigureServiceResponse { + ID: string; + Type: string; + OldConfig?: any; + NewConfig: any; +} \ No newline at end of file diff --git a/src/scalar/ScalarClient.js b/src/scalar/ScalarClient.js deleted file mode 100644 index c6586b2..0000000 --- a/src/scalar/ScalarClient.js +++ /dev/null @@ -1,58 +0,0 @@ -var request = require('request'); -var log = require("../util/LogService"); -var config = require("config"); -var UpstreamConfiguration = require("../UpstreamConfiguration"); - -/** - * Represents a scalar client - */ -class ScalarClient { - - /** - * Creates a new Scalar client - */ - constructor() { - } - - /** - * Registers for a scalar token - * @param {OpenID} openId the open ID to register - * @returns {Promise} resolves to a scalar token - */ - register(openId) { - return this._do("POST", "/register", null, openId).then((response, body) => { - if (response.statusCode !== 200) { - log.error("ScalarClient", response.body); - return Promise.reject(response.body); - } - - return response.body['scalar_token']; - }); - } - - // TODO: Merge this, VectorScalarClient, and MatrixLiteClient into a base class - _do(method, endpoint, qs = null, body = null) { - // TODO: Generify URL - var url = UpstreamConfiguration.getUpstream("vector").url + endpoint; - - log.verbose("ScalarClient", "Performing request: " + url); - - var params = { - url: url, - method: method, - json: body, - qs: qs - }; - - return new Promise((resolve, reject) => { - request(params, (err, response, body) => { - if (err) { - log.error("ScalarClient", err); - reject(err); - } else resolve(response, body); - }); - }); - } -} - -module.exports = new ScalarClient(); \ No newline at end of file diff --git a/src/scalar/ScalarClient.ts b/src/scalar/ScalarClient.ts new file mode 100644 index 0000000..c042a8c --- /dev/null +++ b/src/scalar/ScalarClient.ts @@ -0,0 +1,32 @@ +import { OpenId } from "../models/OpenId"; +import { ScalarRegisterResponse } from "../models/ScalarResponses"; +import * as request from "request"; +import { LogService } from "matrix-js-snippets"; +import Upstream from "../db/models/Upstream"; + +export class ScalarClient { + constructor(private upstream: Upstream) { + } + + public register(openId: OpenId): Promise { + LogService.info("ScalarClient", "Doing upstream scalar request: " + this.upstream.scalarUrl + "/register"); + return new Promise((resolve, reject) => { + request({ + method: "POST", + url: this.upstream.scalarUrl + "/register", + json: openId, + }, (err, res, _body) => { + if (err) { + LogService.error("ScalarClient", "Error registering for token"); + LogService.error("ScalarClient", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("ScalarClient", "Got status code " + res.statusCode + " while registering for token"); + reject(new Error("Could not get token")); + } else { + resolve(res.body); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/scalar/VectorScalarClient.js b/src/scalar/VectorScalarClient.js deleted file mode 100644 index 5129070..0000000 --- a/src/scalar/VectorScalarClient.js +++ /dev/null @@ -1,260 +0,0 @@ -var request = require('request'); -var log = require("../util/LogService"); -var config = require("config"); -var UpstreamConfiguration = require("../UpstreamConfiguration"); - -/** - * Represents a scalar client for vector.im - */ -class VectorScalarClient { - - /** - * Creates a new vector.im Scalar client - */ - constructor() { - } - - /** - * Registers for a scalar token - * @param {OpenID} openId the open ID to register - * @returns {Promise} resolves to a scalar token - */ - register(openId) { - return this._do("POST", "/register", null, openId).then((response, body) => { - var json = JSON.parse(response.body); - return json['scalar_token']; - }); - } - - /** - * Removes a scalar integration - * @param {string} type the type of integration to remove - * @param {string} roomId the room ID to remove it from - * @param {string} scalarToken the upstream scalar token - * @return {Promise<>} resolves when complete - */ - removeIntegration(type, roomId, scalarToken) { - return this._do("POST", "/removeIntegration", {scalar_token: scalarToken}, { - type: type, - room_id: roomId - }).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - // no success processing - }); - } - - /** - * Configures an Integration on Vector - * @param {string} type the integration tpye - * @param {string} scalarToken the scalar token - * @param {*} config the config to POST to the service - * @return {Promise<>} resolves when completed - */ - configureIntegration(type, scalarToken, config) { - return this._do("POST", "/integrations/" + type + "/configureService", {scalar_token: scalarToken}, config).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - // no success processing - }); - } - - /** - * Gets all of the integrations currently in a room - * @param {string} roomId the room ID - * @param {string} scalarToken the scalar token to use - * @returns {Promise<*[]>} resolves a collection of integrations - */ - getIntegrationsForRoom(roomId, scalarToken) { - return this._do("POST", "/integrations", {scalar_token: scalarToken}, {RoomId: roomId}).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - return response.body.integrations; - }); - } - - /** - * Gets information for an integration - * @param {string} type the type to lookup - * @param {string} roomId the room ID to look in - * @param {string} scalarToken the scalar token - * @return {Promise<{bot_user_id:string,integrations:[]}>} resolves to the integration information - */ - getIntegration(type, roomId, scalarToken) { - return this._do("POST", "/integrations/" + type, {scalar_token: scalarToken}, {room_id: roomId}).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - return response.body; - }); - } - - /** - * Gets a list of supported IRC networks - * @param {string} scalarToken the scalar token - * @returns {Promise<{rid: string, title: string, domain: string, id: string}[]>} resolves to the list of IRC networks - */ - getIrcNetworks(scalarToken) { - return this._do("GET", "/bridges/irc/_matrix/provision/querynetworks", {scalar_token: scalarToken}).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - response.body = JSON.parse(response.body); - - var results = []; - for (var network of response.body["replies"]) { - var result = { - rid: network["rid"], - // Assumption: All networks have 1 server from vector - id: network["response"]["servers"][0]["network_id"], - title: network["response"]["servers"][0]["desc"], - domain: network["response"]["servers"][0]["fields"]["domain"] - }; - results.push(result); - } - - return results; - }); - } - - /** - * Gets a list of all linked IRC channels for a given room - * @param {string} roomId the room ID to look in - * @param {string} scalarToken the scalar token - * @returns {Promise<{rid: string, server: string, channel: string}>} resolves to a list of linked channels - */ - getIrcLinks(roomId, scalarToken) { - return this._do("GET", "/bridges/irc/_matrix/provision/listlinks/" + roomId, {scalar_token: scalarToken}).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - response.body = JSON.parse(response.body); - - var results = []; - for (var linkContainer of response.body["replies"]) { - for (var link of linkContainer["response"]) { - results.push({ - rid: linkContainer["rid"], - server: link["remote_room_server"], - channel: link["remote_room_channel"] - }); - } - } - - return results; - }); - } - - /** - * Gets a list of operators in a particular channel on a particular network - * @param {string} rid the network ID - * @param {string} networkServer the server that has the channel on it - * @param {string} channel the channel to look up, with prefix - * @param {string} scalarToken the scalar token - * @returns {Promise} resolves to a list of operators in the channel - */ - getIrcOperators(rid, networkServer, channel, scalarToken) { - return this._do("POST", "/bridges/irc/_matrix/provision/querylink", {scalar_token: scalarToken, rid: rid}, { - remote_room_server: networkServer, - remote_room_channel: channel - }).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - if (response.body["replies"]) { - return response.body["replies"][0]["response"]["operators"]; - } else return Promise.reject("No operators could be found"); - }); - } - - /** - * Requests an operator for permission to link an IRC channel to a matrix room - * @param {string} rid the network ID - * @param {string} roomId the matrix room ID - * @param {string} networkServer the server that has the channel on it - * @param {string} channel the channel to look up, with prefix - * @param {string} operator the channel operator's nick - * @param {string} scalarToken the scalar token - * @returns {Promise<>} resolves when completed - */ - addIrcLink(rid, roomId, networkServer, channel, operator, scalarToken) { - return this._do("POST", "/bridges/irc/_matrix/provision/link", {rid: rid, scalar_token: scalarToken}, { - matrix_room_id: roomId, - remote_room_channel: channel, - remote_room_server: networkServer, - op_nick: operator - }).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - return {status: 'ok'}; - }) - } - - /** - * Removes a channel link from a Matrix room - * @param {string} rid the network ID - * @param {string} roomId the matrix room ID - * @param {string} networkServer the server that has the channel on it - * @param {string} channel the channel to remove, with prefix - * @param {string} scalarToken the scalar token - * @returns {Promise<>} resolves when completed - */ - removeIrcLink(rid, roomId, networkServer, channel, scalarToken) { - return this._do("POST", "/bridges/irc/_matrix/provision/unlink", {rid: rid, scalar_token: scalarToken}, { - matrix_room_id: roomId, - remote_room_channel: channel, - remote_room_server: networkServer - }).then((response, body) => { - if (response.statusCode !== 200) { - log.error("VectorScalarClient", response.body); - return Promise.reject(response.body); - } - - return {status: 'ok'}; - }) - } - - _do(method, endpoint, qs = null, body = null) { - var url = UpstreamConfiguration.getUpstream("vector").url + endpoint; - - log.verbose("VectorScalarClient", "Performing request: " + url); - - var params = { - url: url, - method: method, - json: body, - qs: qs - }; - - return new Promise((resolve, reject) => { - request(params, (err, response, body) => { - if (err) { - log.error("VectorScalarClient", err); - reject(err); - } else resolve(response, body); - }); - }); - } -} - -module.exports = new VectorScalarClient(); \ No newline at end of file diff --git a/src/storage/DimensionStore.js b/src/storage/DimensionStore.js deleted file mode 100644 index a8f0172..0000000 --- a/src/storage/DimensionStore.js +++ /dev/null @@ -1,108 +0,0 @@ -var DBMigrate = require("db-migrate"); -var log = require("./../util/LogService"); -var Sequelize = require('sequelize'); -var dbConfig = require("../../config/database.json"); -var moment = require("moment"); - -/** - * Primary storage for Dimension. - */ -class DimensionStore { - - constructor() { - this._orm = null; - } - - /** - * Prepares the store for use - */ - prepare() { - var env = process.env.NODE_ENV || "development"; - log.info("DimensionStore", "Running migrations"); - return new Promise((resolve, reject)=> { - var dbMigrate = DBMigrate.getInstance(true, { - config: "./config/database.json", - env: env - }); - dbMigrate.up().then(() => { - var dbConfigEnv = dbConfig[env]; - if (!dbConfigEnv) throw new Error("Could not find DB config for " + env); - - var opts = { - host: dbConfigEnv.host || 'localhost', - dialect: 'sqlite', - pool: { - max: 5, - min: 0, - idle: 10000 - }, - storage: dbConfigEnv.filename, - logging: i => log.verbose("DimensionStore [SQL]", i) - }; - - this._orm = new Sequelize(dbConfigEnv.database || 'dimension', dbConfigEnv.username, dbConfigEnv.password, opts); - this._bindModels(); - resolve(); - }, err => { - log.error("DimensionStore", err); - reject(err); - }).catch(err => { - log.error("DimensionStore", err); - reject(err); - }); - }); - } - - _bindModels() { - // Models - this.__Tokens = this._orm.import(__dirname + "/models/tokens"); - - // Relationships - } - - /** - * Creates a new Scalar token - * @param {string} mxid the matrix user id - * @param {OpenID} openId the open ID - * @param {string} scalarToken the token associated with the user - * @param {String?} upstreamToken the upstream scalar token (optional) - * @returns {Promise<>} resolves when complete - */ - createToken(mxid, openId, scalarToken, upstreamToken) { - return this.__Tokens.create({ - matrixUserId: mxid, - matrixServerName: openId.matrix_server_name, - matrixAccessToken: openId.access_token, - scalarToken: scalarToken, - upstreamToken: upstreamToken, - expires: moment().add(openId.expires_in, 'seconds').toDate() - }); - } - - /** - * Checks to determine if a token is valid or not - * @param {string} scalarToken the scalar token to check - * @returns {Promise<>} resolves if valid, rejected otherwise - */ - checkToken(scalarToken) { - return this.__Tokens.find({where: {scalarToken: scalarToken}}).then(token => { - if (!token) return Promise.reject(new Error("Token not found")); - //if (moment().isAfter(moment(token.expires))) return this.__Tokens.destroy({where: {id: token.id}}).then(() => Promise.reject()); - return Promise.resolve(); - }); - } - - /** - * Gets the upstream token for a given scalar token - * @param {string} scalarToken the scalar token to lookup - * @returns {Promise} resolves to the upstream token, or null if not found - */ - getUpstreamToken(scalarToken) { - return this.__Tokens.find({where: {scalarToken: scalarToken}}).then(token => { - if (!token) return null; - return token.upstreamToken; - }); - } -} - -module.exports = DimensionStore; \ No newline at end of file diff --git a/src/storage/models/tokens.js b/src/storage/models/tokens.js deleted file mode 100644 index 75c9167..0000000 --- a/src/storage/models/tokens.js +++ /dev/null @@ -1,45 +0,0 @@ -module.exports = function (sequelize, DataTypes) { - return sequelize.define('tokens', { - id: { - type: DataTypes.INTEGER, - allowNull: false, - autoIncrement: true, - primaryKey: true, - field: 'id' - }, - matrixUserId: { - type: DataTypes.STRING, - allowNull: false, - field: 'matrixUserId' - }, - matrixServerName: { - type: DataTypes.STRING, - allowNull: false, - field: 'matrixServerName' - }, - matrixAccessToken: { - type: DataTypes.STRING, - allowNull: false, - field: 'matrixAccessToken' - }, - scalarToken: { - type: DataTypes.STRING, - allowNull: false, - field: 'scalarToken' - }, - upstreamToken: { - type: DataTypes.STRING, - allowNull: true, - field: 'upstreamToken' - }, - expires: { - type: DataTypes.TIME, - allowNull: false, - field: 'expires' - } - }, { - tableName: 'tokens', - underscored: false, - timestamps: false - }); -}; diff --git a/src/util/LogService.js b/src/util/LogService.js deleted file mode 100644 index e6c040f..0000000 --- a/src/util/LogService.js +++ /dev/null @@ -1,100 +0,0 @@ -var winston = require("winston"); -var chalk = require("chalk"); -var config = require("config"); -var fs = require('fs'); -var moment = require('moment'); - -try { - fs.mkdirSync('logs') -} catch (err) { - if (err.code !== 'EEXIST') throw err -} - -const TERM_COLORS = { - error: "red", - warn: "yellow", - info: "blue", - verbose: "white", - silly: "grey", -}; - -function winstonColorFormatter(options) { - options.level = chalk[TERM_COLORS[options.level]](options.level); - return winstonFormatter(options); -} - -function winstonFormatter(options) { - return options.timestamp() + ' ' + options.level + ' ' + (options.message ? options.message : '') + - (options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '' ); -} - -function getTimestamp() { - return moment().format('MMM-D-YYYY HH:mm:ss.SSS Z'); -} - -var loggingConfig = config.get('logging'); - -var transports = []; -transports.push(new (winston.transports.File)({ - json: false, - name: "file", - filename: loggingConfig.file, - timestamp: getTimestamp, - formatter: winstonFormatter, - level: loggingConfig.fileLevel, - maxsize: loggingConfig.rotate.size, - maxFiles: loggingConfig.rotate.count, - zippedArchive: false -})); - -if (loggingConfig.console) { - transports.push(new (winston.transports.Console)({ - json: false, - name: "console", - timestamp: getTimestamp, - formatter: winstonColorFormatter, - level: loggingConfig.consoleLevel - })); -} - -var log = new winston.Logger({ - transports: transports, - levels: { - error: 0, - warn: 1, - info: 2, - verbose: 3, - silly: 4 - } -}); - -function doLog(level, module, messageOrObject) { - if (typeof(messageOrObject) === 'object' && !(messageOrObject instanceof Error)) - messageOrObject = JSON.stringify(messageOrObject); - var message = "[" + module + "] " + messageOrObject; - log.log(level, message); -} - -class LogService { - static info(module, message) { - doLog('info', module, message); - } - - static warn(module, message) { - doLog('warn', module, message); - } - - static error(module, message) { - doLog('error', module, message); - } - - static verbose(module, message) { - doLog('verbose', module, message); - } - - static silly(module, message) { - doLog('silly', module, message); - } -} - -module.exports = LogService; \ No newline at end of file diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..3a9a0d7 --- /dev/null +++ b/src/version.ts @@ -0,0 +1,20 @@ +import * as git from "git-rev-sync"; + +let version = "Unknown"; +let gitHash = null; + +try { + version = "v" + require("../../package.json").version; +} catch (error) { + // The log service isn't set up by the time we require this file + console.error("version", error); +} + +try { + gitHash = git.short(); +} catch (error) { + // The log service isn't set up by the time we require this file + console.error("version", error); +} + +export const CURRENT_VERSION = version + (gitHash ? "-" + gitHash : ""); diff --git a/tsconfig-app.json b/tsconfig-app.json new file mode 100644 index 0000000..15a18f1 --- /dev/null +++ b/tsconfig-app.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "module": "commonjs", + "moduleResolution": "node", + "target": "es2015", + "noImplicitAny": false, + "sourceMap": true, + "outDir": "./build/app", + "types": [ + "node" + ] + }, + "include": [ + "./src/**/*" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index a7251c2..93d5851 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, - "noEmitHelpers": true, + "noEmitHelpers": false, "noUnusedLocals": true, "noUnusedParameters": true, "lib": ["es2015", "dom"] diff --git a/tslint.json b/tslint.json index 697a78d..a589c97 100644 --- a/tslint.json +++ b/tslint.json @@ -3,13 +3,12 @@ "node_modules/codelyzer" ], "rules": { - "class-name": true, + "class-name": false, "comment-format": [ - true, - "check-space" + true ], "curly": false, - "eofline": true, + "eofline": false, "forin": false, "indent": [ true, @@ -20,7 +19,7 @@ "member-access": false, "member-ordering": [ true, - "static-before-instance", + "static-after-instance", "variables-before-functions" ], "no-arg": true, @@ -54,7 +53,7 @@ "check-else", "check-whitespace" ], - "quotemark": [], + "quotemark": false, "radix": true, "semicolon": [ "always" @@ -107,9 +106,6 @@ ], "no-attribute-parameter-decorator": true, "no-forward-ref": true, - "import-destructuring-spacing": true, - "no-access-missing-member": true, - "templates-use-public": true, - "invoke-injectable": true + "import-destructuring-spacing": true } } \ No newline at end of file diff --git a/web/app/admin/admin.component.html b/web/app/admin/admin.component.html new file mode 100644 index 0000000..ca82593 --- /dev/null +++ b/web/app/admin/admin.component.html @@ -0,0 +1,11 @@ +
    +
  • Dashboard
  • +
  • Widgets
  • +
  • go-neb
  • +
  • Bridges
  • +
+{{ version }} + +
+ +
\ No newline at end of file diff --git a/web/app/admin/admin.component.scss b/web/app/admin/admin.component.scss new file mode 100644 index 0000000..c64395d --- /dev/null +++ b/web/app/admin/admin.component.scss @@ -0,0 +1,60 @@ +//$bgColor: #625F72; +//$hoverColor: #6E8ABC; +//$accentColor: #8ABBFF; + +$bgColor: #233046; +$hoverColor: #374752; +$accentColor: #DE6657; + +.adminNav { + width: 200px; + margin: 0; + padding: 0; + position: fixed; + top: 120px; + left: 0; + bottom: 0; + background-color: $bgColor; +} + +.adminNav li { + list-style: none; + background-color: $bgColor; + border-left: 5px solid $bgColor; + padding: 5px 5px 5px 11px; + color: #fff; + opacity: 0.5; + cursor: pointer; +} + +.adminNav li:hover, +.adminNav li.active { + opacity: 1; + background-color: $hoverColor; + border-left: 5px solid $hoverColor; +} + +.adminNav li.active { + border-left: 5px solid $accentColor; +} + +.adminContent { + position: fixed; + top: 120px; + left: 200px; + bottom: 0; + right: 0; + padding: 15px; + overflow: auto; +} + +.version { + position: fixed; + bottom: 0; + left: 0; + width: 200px; + text-align: center; + color: #7d7d7d; + font-size: 11px; + font-family: monospace; +} \ No newline at end of file diff --git a/web/app/admin/admin.component.ts b/web/app/admin/admin.component.ts new file mode 100644 index 0000000..e7f81ae --- /dev/null +++ b/web/app/admin/admin.component.ts @@ -0,0 +1,35 @@ +import { Component } from "@angular/core"; +import { Router } from "@angular/router"; +import { AdminApiService } from "../shared/services/admin/admin-api.service"; + +@Component({ + templateUrl: "./admin.component.html", + styleUrls: ["./admin.component.scss"], +}) +export class AdminComponent { + + public version = ""; + + constructor(private router: Router, adminApi: AdminApiService) { + adminApi.getVersion().then(r => this.version = r.version); + } + + public goto(adminRoute: string) { + const currentRoute = this.router.routerState.snapshot.url; + const baseRoute = currentRoute.substring(0, currentRoute.indexOf("/admin")); + + const route = adminRoute ? [baseRoute, "admin", adminRoute] : [baseRoute, "admin"]; + this.router.navigate(route); + } + + public isActive(adminRoute: string, exact?: boolean) { + let currentRoute = this.router.routerState.snapshot.url; + currentRoute = currentRoute.substring(currentRoute.indexOf('/admin')); + + // Specifically for the dashboard handling + if (adminRoute === "" && !currentRoute.endsWith("/")) currentRoute = currentRoute + "/"; + + if (exact) return currentRoute === "/admin/" + adminRoute; + return currentRoute.startsWith("/admin/" + adminRoute); + } +} diff --git a/web/app/admin/bridges/bridges.component.html b/web/app/admin/bridges/bridges.component.html new file mode 100644 index 0000000..af111eb --- /dev/null +++ b/web/app/admin/bridges/bridges.component.html @@ -0,0 +1,37 @@ +
+ +
+
+ +
+

+ Bridges provide a way for rooms to interact with and/or bring in events from a third party network. For + example, an IRC bridge can allow IRC and matrix users to communicate with each other. +

+ + + + + + + + + + + + + + + + + + + +
NameDescriptionActions
No bridges.
{{ bridge.displayName }}{{ bridge.description }} + + + +
+
+
+
\ No newline at end of file diff --git a/web/app/admin/bridges/bridges.component.scss b/web/app/admin/bridges/bridges.component.scss new file mode 100644 index 0000000..847d207 --- /dev/null +++ b/web/app/admin/bridges/bridges.component.scss @@ -0,0 +1,8 @@ +tr td:last-child { + vertical-align: middle; +} + +.appsvcConfigButton, +.editButton { + cursor: pointer; +} \ No newline at end of file diff --git a/web/app/admin/bridges/bridges.component.ts b/web/app/admin/bridges/bridges.component.ts new file mode 100644 index 0000000..216eebc --- /dev/null +++ b/web/app/admin/bridges/bridges.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { FE_Bridge } from "../../shared/models/integration"; +import { AdminIntegrationsApiService } from "../../shared/services/admin/admin-integrations-api.service"; + +@Component({ + templateUrl: "./bridges.component.html", + styleUrls: ["./bridges.component.scss"], +}) +export class AdminBridgesComponent implements OnInit { + + public isLoading = true; + public bridges: FE_Bridge[]; + + constructor(private adminIntegrations: AdminIntegrationsApiService, + private toaster: ToasterService) { + } + + public ngOnInit() { + this.adminIntegrations.getAllBridges().then(bridges => { + this.bridges = bridges.filter(b => b.isEnabled); + this.isLoading = false; + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Failed to load bridges"); + }); + } +} diff --git a/web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.html b/web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.html new file mode 100644 index 0000000..b9d0d3c --- /dev/null +++ b/web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.html @@ -0,0 +1,24 @@ +
+
+

Add a new self-hosted IRC Bridge

+
+
+

Self-hosted IRC bridges must have provisioning enabled in the configuration.

+ + +
+ +
\ No newline at end of file diff --git a/db/.gitkeep b/web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.scss similarity index 100% rename from db/.gitkeep rename to web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.scss diff --git a/web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.ts b/web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.ts new file mode 100644 index 0000000..c26e7af --- /dev/null +++ b/web/app/admin/bridges/irc/add-selfhosted/add-selfhosted.component.ts @@ -0,0 +1,35 @@ +import { Component } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { BSModalContext } from "ngx-modialog/plugins/bootstrap"; +import { AdminIrcApiService } from "../../../../shared/services/admin/admin-irc-api.service"; + +export class AddSelfhostedIrcBridgeDialogContext extends BSModalContext { +} + +@Component({ + templateUrl: "./add-selfhosted.component.html", + styleUrls: ["./add-selfhosted.component.scss"], +}) +export class AdminIrcBridgeAddSelfhostedComponent implements ModalComponent { + + public isSaving = false; + public provisionUrl: string; + + constructor(public dialog: DialogRef, + private ircApi: AdminIrcApiService, + private toaster: ToasterService) { + } + + public add() { + this.isSaving = true; + this.ircApi.newSelfhosted(this.provisionUrl).then(() => { + this.toaster.pop("success", "IRC Bridge added"); + this.dialog.close(); + }).catch(err => { + console.error(err); + this.isSaving = false; + this.toaster.pop("error", "Failed to create IRC bridge"); + }); + } +} diff --git a/web/app/admin/bridges/irc/irc.component.html b/web/app/admin/bridges/irc/irc.component.html new file mode 100644 index 0000000..5a324f7 --- /dev/null +++ b/web/app/admin/bridges/irc/irc.component.html @@ -0,0 +1,49 @@ +
+ +
+
+ +
+

+ matrix-appservice-irc + is an IRC bridge that supports multiple IRC networks. Dimension is capable of using multiple IRC + bridges to better distribute the load across multiple networks in large deployments. +

+ + + + + + + + + + + + + + + + + + + +
NameEnabled NetworksActions
No bridge configurations.
+ {{ bridge.upstreamId ? "matrix.org's bridge" : "Self-hosted bridge" }} + ({{ bridge.provisionUrl }}) + + {{ getEnabledNetworksString(bridge) }} + + + + +
+ + +
+
+
\ No newline at end of file diff --git a/web/app/admin/bridges/irc/irc.component.scss b/web/app/admin/bridges/irc/irc.component.scss new file mode 100644 index 0000000..788d7ed --- /dev/null +++ b/web/app/admin/bridges/irc/irc.component.scss @@ -0,0 +1,3 @@ +.editButton { + cursor: pointer; +} \ No newline at end of file diff --git a/web/app/admin/bridges/irc/irc.component.ts b/web/app/admin/bridges/irc/irc.component.ts new file mode 100644 index 0000000..ab46c26 --- /dev/null +++ b/web/app/admin/bridges/irc/irc.component.ts @@ -0,0 +1,113 @@ +import { Component, OnInit } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { AdminIrcApiService } from "../../../shared/services/admin/admin-irc-api.service"; +import { FE_Upstream } from "../../../shared/models/admin-responses"; +import { AdminUpstreamApiService } from "../../../shared/services/admin/admin-upstream-api.service"; +import { FE_IrcBridge } from "../../../shared/models/irc"; +import { Modal, overlayConfigFactory } from "ngx-modialog"; +import { AdminIrcBridgeNetworksComponent, IrcNetworksDialogContext } from "./networks/networks.component"; +import { + AddSelfhostedIrcBridgeDialogContext, + AdminIrcBridgeAddSelfhostedComponent +} from "./add-selfhosted/add-selfhosted.component"; + +@Component({ + templateUrl: "./irc.component.html", + styleUrls: ["./irc.component.scss"], +}) +export class AdminIrcBridgeComponent implements OnInit { + + public isLoading = true; + public isUpdating = false; + public hasModularBridge = false; + public configurations: FE_IrcBridge[] = []; + + private upstreams: FE_Upstream[]; + + constructor(private upstreamApi: AdminUpstreamApiService, + private ircApi: AdminIrcApiService, + private toaster: ToasterService, + private modal: Modal) { + } + + public ngOnInit() { + this.reload().then(() => this.isLoading = false); + } + + private async reload(): Promise { + try { + this.upstreams = await this.upstreamApi.getUpstreams(); + this.configurations = await this.ircApi.getBridges(); + + this.hasModularBridge = false; + for (const bridge of this.configurations) { + if (bridge.upstreamId) { + this.hasModularBridge = true; + break; + } + } + } catch (err) { + console.error(err); + this.toaster.pop("error", "Error loading bridges"); + } + } + + public getEnabledNetworksString(bridge: FE_IrcBridge): string { + const networkIds = Object.keys(bridge.availableNetworks); + const result = networkIds.filter(i => bridge.availableNetworks[i].isEnabled) + .map(i => bridge.availableNetworks[i].name) + .join(", "); + if (!result) return "None"; + return result; + } + + public addModularHostedBridge() { + this.isUpdating = true; + + const createBridge = (upstream: FE_Upstream) => { + return this.ircApi.newFromUpstream(upstream).then(bridge => { + this.configurations.push(bridge); + this.toaster.pop("success", "matrix.org's IRC bridge added", "Click the pencil icon to enable networks."); + this.isUpdating = false; + this.hasModularBridge = true; + }).catch(err => { + console.error(err); + this.isUpdating = false; + this.toaster.pop("error", "Error adding matrix.org's IRC Bridge"); + }); + }; + const vectorUpstreams = this.upstreams.filter(u => u.type === "vector"); + if (vectorUpstreams.length === 0) { + console.log("Creating default scalar upstream"); + const scalarUrl = "https://scalar.vector.im/api"; + this.upstreamApi.newUpstream("modular", "vector", scalarUrl, scalarUrl).then(upstream => { + this.upstreams.push(upstream); + createBridge(upstream); + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Error creating matrix.org's IRC Bridge"); + }); + } else createBridge(vectorUpstreams[0]); + } + + public addSelfHostedBridge() { + this.modal.open(AdminIrcBridgeAddSelfhostedComponent, overlayConfigFactory({ + isBlocking: true, + size: 'lg', + }, AddSelfhostedIrcBridgeDialogContext)).result.then(() => { + this.reload().catch(err => { + console.error(err); + this.toaster.pop("error", "Failed to get an update IRC bridge list"); + }); + }); + } + + public editNetworks(bridge: FE_IrcBridge) { + this.modal.open(AdminIrcBridgeNetworksComponent, overlayConfigFactory({ + bridge: bridge, + + isBlocking: true, + size: 'lg', + }, IrcNetworksDialogContext)); + } +} diff --git a/web/app/admin/bridges/irc/networks/networks.component.html b/web/app/admin/bridges/irc/networks/networks.component.html new file mode 100644 index 0000000..b93d7e0 --- /dev/null +++ b/web/app/admin/bridges/irc/networks/networks.component.html @@ -0,0 +1,29 @@ +
+
+

{{ bridge.upstreamId ? "matrix.org's" : "Self-hosted" }} IRC Bridge Networks

+
+
+ + + + + + + + + + + + + +
NetworkEnabled
{{ network.name }} + +
+
+ +
\ No newline at end of file diff --git a/web/app/admin/bridges/irc/networks/networks.component.scss b/web/app/admin/bridges/irc/networks/networks.component.scss new file mode 100644 index 0000000..344be0c --- /dev/null +++ b/web/app/admin/bridges/irc/networks/networks.component.scss @@ -0,0 +1,5 @@ +table tr th:last-child, +table tr td:last-child { + text-align: center; + width: 100px; +} \ No newline at end of file diff --git a/web/app/admin/bridges/irc/networks/networks.component.ts b/web/app/admin/bridges/irc/networks/networks.component.ts new file mode 100644 index 0000000..d65b821 --- /dev/null +++ b/web/app/admin/bridges/irc/networks/networks.component.ts @@ -0,0 +1,63 @@ +import { Component } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { BSModalContext } from "ngx-modialog/plugins/bootstrap"; +import { FE_IrcBridge } from "../../../../shared/models/irc"; +import { AdminIrcApiService } from "../../../../shared/services/admin/admin-irc-api.service"; + +export class IrcNetworksDialogContext extends BSModalContext { + public bridge: FE_IrcBridge; +} + +interface LocalNetwork { + id: string; + name: string; + domain: string; + bridgeUserId: string; + isEnabled: boolean; +} + +@Component({ + templateUrl: "./networks.component.html", + styleUrls: ["./networks.component.scss"], +}) +export class AdminIrcBridgeNetworksComponent implements ModalComponent { + + public isUpdating = false; + public bridge: FE_IrcBridge; + public networks: LocalNetwork[]; + + constructor(public dialog: DialogRef, + private ircApi: AdminIrcApiService, + private toaster: ToasterService) { + this.bridge = dialog.context.bridge; + + const networkIds = Object.keys(this.bridge.availableNetworks); + this.networks = networkIds.map(i => { + return { + id: i, + name: this.bridge.availableNetworks[i].name, + domain: this.bridge.availableNetworks[i].domain, + bridgeUserId: this.bridge.availableNetworks[i].bridgeUserId, + isEnabled: this.bridge.availableNetworks[i].isEnabled, + }; + }); + } + + public toggleNetwork(network: LocalNetwork) { + network.isEnabled = !network.isEnabled; + this.bridge.availableNetworks[network.id].isEnabled = network.isEnabled; + + this.isUpdating = true; + this.ircApi.setNetworkEnabled(this.bridge.id, network.id, network.isEnabled).then(() => { + this.isUpdating = false; + this.toaster.pop("success", "Network " + (network.isEnabled ? "enabled" : "disabled")); + }).catch(err => { + console.error(err); + this.isUpdating = false; + network.isEnabled = !network.isEnabled; + this.bridge.availableNetworks[network.id].isEnabled = network.isEnabled; + this.toaster.pop("error", "Failed to update network"); + }); + } +} diff --git a/web/app/admin/home/home.component.html b/web/app/admin/home/home.component.html new file mode 100644 index 0000000..6d035ff --- /dev/null +++ b/web/app/admin/home/home.component.html @@ -0,0 +1,32 @@ +
+ +
+
+ +
+

Parts of your configuration are displayed below. To change these values, edit your configuration and restart Dimension.

+ +
+
+ Administrators +
    +
  • {{ user }}
  • +
+
+
+ Widget Blacklist +
    +
  • {{ ip }}
  • +
+
+
+ Homeserver
+ Name: {{ config.homeserver.name }}
+ Federation URL: {{ config.homeserver.federationUrl }}
+ Client/Server URL: {{ config.homeserver.clientServerUrl }}
+ Utility User ID: {{ config.homeserver.userId }} +
+
+
+
+
\ No newline at end of file diff --git a/web/app/admin/home/home.component.scss b/web/app/admin/home/home.component.scss new file mode 100644 index 0000000..af93ad3 --- /dev/null +++ b/web/app/admin/home/home.component.scss @@ -0,0 +1,3 @@ +ul { + padding-left: 25px; +} \ No newline at end of file diff --git a/web/app/admin/home/home.component.ts b/web/app/admin/home/home.component.ts new file mode 100644 index 0000000..4e3d3aa --- /dev/null +++ b/web/app/admin/home/home.component.ts @@ -0,0 +1,20 @@ +import { Component } from "@angular/core"; +import { AdminApiService } from "../../shared/services/admin/admin-api.service"; +import { FE_DimensionConfig } from "../../shared/models/admin-responses"; + +@Component({ + templateUrl: "./home.component.html", + styleUrls: ["./home.component.scss"], +}) +export class AdminHomeComponent { + + public isLoading = true; + public config: FE_DimensionConfig; + + constructor(adminApi: AdminApiService) { + adminApi.getConfig().then(config => { + this.config = config; + this.isLoading = false; + }); + } +} diff --git a/web/app/admin/neb/add-selfhosted/add-selfhosted.component.html b/web/app/admin/neb/add-selfhosted/add-selfhosted.component.html new file mode 100644 index 0000000..b868a69 --- /dev/null +++ b/web/app/admin/neb/add-selfhosted/add-selfhosted.component.html @@ -0,0 +1,24 @@ + +
+

Self-hosted go-neb instances are powered by application services installed on your homeserver. The application service is responsible for creating the bots dynamically.

+ + + + + +
+
\ No newline at end of file diff --git a/web/app/admin/neb/add-selfhosted/add-selfhosted.component.scss b/web/app/admin/neb/add-selfhosted/add-selfhosted.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/admin/neb/add-selfhosted/add-selfhosted.component.ts b/web/app/admin/neb/add-selfhosted/add-selfhosted.component.ts new file mode 100644 index 0000000..d2a7dbc --- /dev/null +++ b/web/app/admin/neb/add-selfhosted/add-selfhosted.component.ts @@ -0,0 +1,50 @@ +import { Component } from "@angular/core"; +import { AdminAppserviceApiService } from "../../../shared/services/admin/admin-appservice-api.service"; +import { AdminNebApiService } from "../../../shared/services/admin/admin-neb-api.service"; +import { ToasterService } from "angular2-toaster"; +import { ActivatedRoute, Router } from "@angular/router"; +import { Modal, overlayConfigFactory } from "ngx-modialog"; +import { + AdminNebAppserviceConfigComponent, + AppserviceConfigDialogContext +} from "../appservice-config/appservice-config.component"; + + +@Component({ + templateUrl: "./add-selfhosted.component.html", + styleUrls: ["./add-selfhosted.component.scss"], +}) +export class AdminAddSelfhostedNebComponent { + + public isSaving = false; + public userPrefix = "@_neb"; + public adminUrl = "http://localhost:4050"; + + constructor(private asApi: AdminAppserviceApiService, + private nebApi: AdminNebApiService, + private toaster: ToasterService, + private router: Router, + private activatedRoute: ActivatedRoute, + private modal: Modal) { + } + + public save(): void { + this.isSaving = true; + this.asApi.createAppservice(this.userPrefix).then(appservice => { + return this.nebApi.newAppserviceConfiguration(this.adminUrl, appservice); + }).then(neb => { + this.toaster.pop("success", "New go-neb created"); + + this.modal.open(AdminNebAppserviceConfigComponent, overlayConfigFactory({ + neb: neb, + + isBlocking: true, + size: 'lg', + }, AppserviceConfigDialogContext)).result.then(() => this.router.navigate(["../.."], {relativeTo: this.activatedRoute})); + }).catch(err => { + console.error(err); + this.isSaving = false; + this.toaster.pop("error", "Error creating appservice"); + }); + } +} diff --git a/web/app/admin/neb/appservice-config/appservice-config.component.html b/web/app/admin/neb/appservice-config/appservice-config.component.html new file mode 100644 index 0000000..97b9433 --- /dev/null +++ b/web/app/admin/neb/appservice-config/appservice-config.component.html @@ -0,0 +1,22 @@ +
+
+

go-neb appservice configuration

+
+
+ +
+
+ Copy and paste this configuration to appservice-{{appservice.id}}.yaml on your homeserver and + register it as an application service. +
+
{{appserviceConfig}}
+
+ +
\ No newline at end of file diff --git a/web/app/admin/neb/appservice-config/appservice-config.component.scss b/web/app/admin/neb/appservice-config/appservice-config.component.scss new file mode 100644 index 0000000..779f17d --- /dev/null +++ b/web/app/admin/neb/appservice-config/appservice-config.component.scss @@ -0,0 +1,6 @@ +pre { + margin-top: 10px; + padding: 5px; + border: 1px solid #ccc; + background-color: #eee; +} \ No newline at end of file diff --git a/web/app/admin/neb/appservice-config/appservice-config.component.ts b/web/app/admin/neb/appservice-config/appservice-config.component.ts new file mode 100644 index 0000000..a9693f7 --- /dev/null +++ b/web/app/admin/neb/appservice-config/appservice-config.component.ts @@ -0,0 +1,57 @@ +import { Component } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { FE_Appservice, FE_NebConfiguration } from "../../../shared/models/admin-responses"; +import { AdminAppserviceApiService } from "../../../shared/services/admin/admin-appservice-api.service"; +import { BSModalContext } from "ngx-modialog/plugins/bootstrap"; + +export class AppserviceConfigDialogContext extends BSModalContext { + public neb: FE_NebConfiguration; +} + +@Component({ + templateUrl: "./appservice-config.component.html", + styleUrls: ["./appservice-config.component.scss"], +}) +export class AdminNebAppserviceConfigComponent implements ModalComponent { + + public isLoading = true; + public neb: FE_NebConfiguration; + public appservice: FE_Appservice; + + constructor(public dialog: DialogRef, private adminAppserviceApi: AdminAppserviceApiService, private toaster: ToasterService) { + this.neb = dialog.context.neb; + + this.adminAppserviceApi.getAppservice(this.neb.appserviceId).then(appservice => { + this.appservice = appservice; + this.isLoading = false; + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Could not load appservice configuration"); + }); + } + + public get appserviceConfig(): string { + return `` + + `id: ${this.appservice.id}\n` + + `url: ${window.location.origin}/_matrix/appservice/r0\n` + + `as_token: ${this.appservice.asToken}\n` + + `hs_token: ${this.appservice.hsToken}\n` + + `sender_localpart: ${this.appservice.userPrefix}\n` + + `namespaces:\n` + + ` users:\n` + + ` - exclusive: true\n` + + ` regex: '@${this.appservice.userPrefix}.*'\n` + + ` aliases: []\n` + + ` rooms: []`; + } + + public test() { + this.adminAppserviceApi.test(this.neb.appserviceId).then(() => { + this.toaster.pop("success", "The appservice appears to be correctly set up"); + }).catch(err => { + console.error(err); + this.toaster.pop("error", "The appservice is not correctly set up"); + }); + } +} diff --git a/web/app/admin/neb/config/config-context.ts b/web/app/admin/neb/config/config-context.ts new file mode 100644 index 0000000..cc3299e --- /dev/null +++ b/web/app/admin/neb/config/config-context.ts @@ -0,0 +1,8 @@ +import { BSModalContext } from "ngx-modialog/plugins/bootstrap"; +import { FE_NebConfiguration } from "../../../shared/models/admin-responses"; +import { FE_Integration } from "../../../shared/models/integration"; + +export class NebBotConfigurationDialogContext extends BSModalContext { + public integration: FE_Integration; + public neb: FE_NebConfiguration; +} \ No newline at end of file diff --git a/web/app/admin/neb/config/config-dialog.scss b/web/app/admin/neb/config/config-dialog.scss new file mode 100644 index 0000000..0cb632f --- /dev/null +++ b/web/app/admin/neb/config/config-dialog.scss @@ -0,0 +1,3 @@ +.label-block { + margin-bottom: 15px; +} \ No newline at end of file diff --git a/web/app/admin/neb/config/giphy/giphy.component.html b/web/app/admin/neb/config/giphy/giphy.component.html new file mode 100644 index 0000000..828ca1f --- /dev/null +++ b/web/app/admin/neb/config/giphy/giphy.component.html @@ -0,0 +1,33 @@ +
+
+

Giphy Configuration

+
+
+ +
+
+ + +
+ +
\ No newline at end of file diff --git a/web/app/admin/neb/config/giphy/giphy.component.scss b/web/app/admin/neb/config/giphy/giphy.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/admin/neb/config/giphy/giphy.component.ts b/web/app/admin/neb/config/giphy/giphy.component.ts new file mode 100644 index 0000000..eaee6ce --- /dev/null +++ b/web/app/admin/neb/config/giphy/giphy.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { NebBotConfigurationDialogContext } from "../config-context"; +import { AdminNebApiService } from "../../../../shared/services/admin/admin-neb-api.service"; +import { FE_NebConfiguration } from "../../../../shared/models/admin-responses"; +import { FE_Integration } from "../../../../shared/models/integration"; + +interface GiphyConfig { + api_key: string; + use_downsized: boolean; +} + +@Component({ + templateUrl: "./giphy.component.html", + styleUrls: ["./giphy.component.scss", "../config-dialog.scss"], +}) +export class AdminNebGiphyConfigComponent implements ModalComponent, OnInit { + + public isLoading = true; + public isUpdating = false; + public config: GiphyConfig; + public integration: FE_Integration; + public neb: FE_NebConfiguration; + + constructor(public dialog: DialogRef, + private adminNebApi: AdminNebApiService, + private toaster: ToasterService) { + this.neb = dialog.context.neb; + this.integration = dialog.context.integration; + } + + public ngOnInit() { + this.adminNebApi.getIntegrationConfiguration(this.neb.id, this.integration.type).then(config => { + this.config = config; + this.isLoading = false; + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Error loading configuration"); + }); + } + + public save() { + this.isUpdating = true; + this.adminNebApi.setIntegrationConfiguration(this.neb.id, this.integration.type, this.config).then(() => { + this.toaster.pop("success", "Configuration updated"); + this.dialog.close(); + }).catch(err => { + this.isUpdating = false; + console.error(err); + this.toaster.pop("error", "Error updating integration"); + }); + } +} diff --git a/web/app/admin/neb/config/google/google.component.html b/web/app/admin/neb/config/google/google.component.html new file mode 100644 index 0000000..1ce043f --- /dev/null +++ b/web/app/admin/neb/config/google/google.component.html @@ -0,0 +1,32 @@ +
+
+

Google Configuration

+
+
+ +
+
+ + +
+ +
\ No newline at end of file diff --git a/web/app/admin/neb/config/google/google.component.scss b/web/app/admin/neb/config/google/google.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/admin/neb/config/google/google.component.ts b/web/app/admin/neb/config/google/google.component.ts new file mode 100644 index 0000000..6126c69 --- /dev/null +++ b/web/app/admin/neb/config/google/google.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { NebBotConfigurationDialogContext } from "../config-context"; +import { AdminNebApiService } from "../../../../shared/services/admin/admin-neb-api.service"; +import { FE_NebConfiguration } from "../../../../shared/models/admin-responses"; +import { FE_Integration } from "../../../../shared/models/integration"; + +interface GoogleConfig { + api_key: string; + cx: string; // search engine id +} + +@Component({ + templateUrl: "./google.component.html", + styleUrls: ["./google.component.scss", "../config-dialog.scss"], +}) +export class AdminNebGoogleConfigComponent implements ModalComponent, OnInit { + + public isLoading = true; + public isUpdating = false; + public config: GoogleConfig; + public integration: FE_Integration; + public neb: FE_NebConfiguration; + + constructor(public dialog: DialogRef, + private adminNebApi: AdminNebApiService, + private toaster: ToasterService) { + this.neb = dialog.context.neb; + this.integration = dialog.context.integration; + } + + public ngOnInit() { + this.adminNebApi.getIntegrationConfiguration(this.neb.id, this.integration.type).then(config => { + this.config = config; + this.isLoading = false; + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Error loading configuration"); + }); + } + + public save() { + this.isUpdating = true; + this.adminNebApi.setIntegrationConfiguration(this.neb.id, this.integration.type, this.config).then(() => { + this.toaster.pop("success", "Configuration updated"); + this.dialog.close(); + }).catch(err => { + this.isUpdating = false; + console.error(err); + this.toaster.pop("error", "Error updating integration"); + }); + } +} diff --git a/web/app/admin/neb/config/guggy/guggy.component.html b/web/app/admin/neb/config/guggy/guggy.component.html new file mode 100644 index 0000000..a868714 --- /dev/null +++ b/web/app/admin/neb/config/guggy/guggy.component.html @@ -0,0 +1,25 @@ +
+
+

Guggy Configuration

+
+
+ +
+
+ +
+ +
\ No newline at end of file diff --git a/web/app/admin/neb/config/guggy/guggy.component.scss b/web/app/admin/neb/config/guggy/guggy.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/admin/neb/config/guggy/guggy.component.ts b/web/app/admin/neb/config/guggy/guggy.component.ts new file mode 100644 index 0000000..9517de2 --- /dev/null +++ b/web/app/admin/neb/config/guggy/guggy.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { NebBotConfigurationDialogContext } from "../config-context"; +import { AdminNebApiService } from "../../../../shared/services/admin/admin-neb-api.service"; +import { FE_NebConfiguration } from "../../../../shared/models/admin-responses"; +import { FE_Integration } from "../../../../shared/models/integration"; + +interface GuggyConfig { + api_key: string; +} + +@Component({ + templateUrl: "./guggy.component.html", + styleUrls: ["./guggy.component.scss", "../config-dialog.scss"], +}) +export class AdminNebGuggyConfigComponent implements ModalComponent, OnInit { + + public isLoading = true; + public isUpdating = false; + public config: GuggyConfig; + public integration: FE_Integration; + public neb: FE_NebConfiguration; + + constructor(public dialog: DialogRef, + private adminNebApi: AdminNebApiService, + private toaster: ToasterService) { + this.neb = dialog.context.neb; + this.integration = dialog.context.integration; + } + + public ngOnInit() { + this.adminNebApi.getIntegrationConfiguration(this.neb.id, this.integration.type).then(config => { + this.config = config; + this.isLoading = false; + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Error loading configuration"); + }); + } + + public save() { + this.isUpdating = true; + this.adminNebApi.setIntegrationConfiguration(this.neb.id, this.integration.type, this.config).then(() => { + this.toaster.pop("success", "Configuration updated"); + this.dialog.close(); + }).catch(err => { + this.isUpdating = false; + console.error(err); + this.toaster.pop("error", "Error updating integration"); + }); + } +} diff --git a/web/app/admin/neb/config/imgur/imgur.component.html b/web/app/admin/neb/config/imgur/imgur.component.html new file mode 100644 index 0000000..ed4e138 --- /dev/null +++ b/web/app/admin/neb/config/imgur/imgur.component.html @@ -0,0 +1,32 @@ +
+
+

Imgur Configuration

+
+
+ +
+
+ + +
+ +
\ No newline at end of file diff --git a/web/app/admin/neb/config/imgur/imgur.component.scss b/web/app/admin/neb/config/imgur/imgur.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/admin/neb/config/imgur/imgur.component.ts b/web/app/admin/neb/config/imgur/imgur.component.ts new file mode 100644 index 0000000..ac7c404 --- /dev/null +++ b/web/app/admin/neb/config/imgur/imgur.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { NebBotConfigurationDialogContext } from "../config-context"; +import { AdminNebApiService } from "../../../../shared/services/admin/admin-neb-api.service"; +import { FE_NebConfiguration } from "../../../../shared/models/admin-responses"; +import { FE_Integration } from "../../../../shared/models/integration"; + +interface ImgurConfig { + client_id: string; + client_secret: string; +} + +@Component({ + templateUrl: "./imgur.component.html", + styleUrls: ["./imgur.component.scss", "../config-dialog.scss"], +}) +export class AdminNebImgurConfigComponent implements ModalComponent, OnInit { + + public isLoading = true; + public isUpdating = false; + public config: ImgurConfig; + public integration: FE_Integration; + public neb: FE_NebConfiguration; + + constructor(public dialog: DialogRef, + private adminNebApi: AdminNebApiService, + private toaster: ToasterService) { + this.neb = dialog.context.neb; + this.integration = dialog.context.integration; + } + + public ngOnInit() { + this.adminNebApi.getIntegrationConfiguration(this.neb.id, this.integration.type).then(config => { + this.config = config; + this.isLoading = false; + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Error loading configuration"); + }); + } + + public save() { + this.isUpdating = true; + this.adminNebApi.setIntegrationConfiguration(this.neb.id, this.integration.type, this.config).then(() => { + this.toaster.pop("success", "Configuration updated"); + this.dialog.close(); + }).catch(err => { + this.isUpdating = false; + console.error(err); + this.toaster.pop("error", "Error updating integration"); + }); + } +} diff --git a/web/app/admin/neb/edit/edit.component.html b/web/app/admin/neb/edit/edit.component.html new file mode 100644 index 0000000..3d50063 --- /dev/null +++ b/web/app/admin/neb/edit/edit.component.html @@ -0,0 +1,37 @@ +
+ +
+
+ +
+

go-neb supports many different types of bots, each of which is listed below. Here you can configure which + bots this go-neb instance should use.

+ + + + + + + + + + + + + + + + +
NameDescriptionActions
{{ bot.displayName }}{{ bot.description }} + + + + + +
+
+
+
\ No newline at end of file diff --git a/web/app/admin/neb/edit/edit.component.scss b/web/app/admin/neb/edit/edit.component.scss new file mode 100644 index 0000000..62d2182 --- /dev/null +++ b/web/app/admin/neb/edit/edit.component.scss @@ -0,0 +1,4 @@ +.editButton { + cursor: pointer; + vertical-align: text-bottom; +} \ No newline at end of file diff --git a/web/app/admin/neb/edit/edit.component.ts b/web/app/admin/neb/edit/edit.component.ts new file mode 100644 index 0000000..2b1e2a5 --- /dev/null +++ b/web/app/admin/neb/edit/edit.component.ts @@ -0,0 +1,127 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { FE_NebConfiguration } from "../../../shared/models/admin-responses"; +import { AdminNebApiService } from "../../../shared/services/admin/admin-neb-api.service"; +import { ActivatedRoute } from "@angular/router"; +import { ToasterService } from "angular2-toaster"; +import { FE_Integration } from "../../../shared/models/integration"; +import { NEB_HAS_CONFIG, NEB_IS_COMPLEX } from "../../../shared/models/neb"; +import { Modal, overlayConfigFactory } from "ngx-modialog"; +import { AdminNebGiphyConfigComponent } from "../config/giphy/giphy.component"; +import { NebBotConfigurationDialogContext } from "../config/config-context"; +import { ContainerContent } from "ngx-modialog/src/models/tokens"; +import { AdminNebGuggyConfigComponent } from "../config/guggy/guggy.component"; +import { AdminNebGoogleConfigComponent } from "../config/google/google.component"; +import { AdminNebImgurConfigComponent } from "../config/imgur/imgur.component"; + + +@Component({ + templateUrl: "./edit.component.html", + styleUrls: ["./edit.component.scss"], +}) +export class AdminEditNebComponent implements OnInit, OnDestroy { + + public isLoading = true; + public isUpdating = false; + public isUpstream = false; + public nebConfig: FE_NebConfiguration; + + private subscription: any; + private overlappingTypes: string[] = []; + + constructor(private nebApi: AdminNebApiService, + private route: ActivatedRoute, + private modal: Modal, + private toaster: ToasterService) { + } + + public ngOnInit() { + this.subscription = this.route.params.subscribe(params => { + this.loadNeb(Number(params["nebId"])); + }); + } + + public ngOnDestroy() { + this.subscription.unsubscribe(); + } + + public isOverlapping(bot: FE_Integration) { + return this.overlappingTypes.indexOf(bot.type) !== -1; + } + + public hasConfig(bot: FE_Integration): boolean { + return NEB_HAS_CONFIG.indexOf(bot.type) !== -1 && !this.isUpstream; + } + + public async toggleBot(bot: FE_Integration) { + bot.isEnabled = !bot.isEnabled; + this.isUpdating = true; + + try { + await this.nebApi.toggleIntegration(this.nebConfig.id, bot.type, bot.isEnabled); + this.isUpdating = false; + this.toaster.pop("success", "Integration updated"); + } catch (err) { + console.error(err); + bot.isEnabled = !bot.isEnabled; // revert change + this.isUpdating = false; + this.toaster.pop("error", "Error updating integration"); + return; + } + + // Only update the service configuration if + if (bot.isEnabled && !this.isUpstream) { + if (this.hasConfig(bot)) { + this.editBot(bot); + } else if (NEB_IS_COMPLEX.indexOf(bot.type) === -1) { + try { + await this.nebApi.setIntegrationConfiguration(this.nebConfig.id, bot.type, {}); + } catch (err) { + console.error(err); + this.toaster.pop("warning", "Failed to configure the integration", "Manual troubleshooting may be requred"); + return; + } + } + } + } + + public editBot(bot: FE_Integration) { + let component: ContainerContent; + + if (bot.type === "giphy") component = AdminNebGiphyConfigComponent; + if (bot.type === "guggy") component = AdminNebGuggyConfigComponent; + if (bot.type === "google") component = AdminNebGoogleConfigComponent; + if (bot.type === "imgur") component = AdminNebImgurConfigComponent; + + if (!component) throw new Error("No config component for " + bot.type); + this.modal.open(component, overlayConfigFactory({ + neb: this.nebConfig, + integration: bot, + + isBlocking: true, + size: 'lg', + }, NebBotConfigurationDialogContext)); + } + + private loadNeb(nebId: number) { + this.isLoading = true; + this.nebApi.getConfigurations().then(configs => { + const handledTypes: string[] = []; + for (const config of configs) { + if (config.id === nebId) { + this.nebConfig = config; + } else { + for (const type of config.integrations) { + if (type.isEnabled) handledTypes.push(type.type); + } + } + } + + this.overlappingTypes = handledTypes; + this.isUpstream = !!this.nebConfig.upstreamId; + this.isLoading = false; + }).catch(err => { + console.error(err); + this.toaster.pop('error', "Could not get go-neb configuration"); + }); + } +} diff --git a/web/app/admin/neb/neb.component.html b/web/app/admin/neb/neb.component.html new file mode 100644 index 0000000..6c0bdfe --- /dev/null +++ b/web/app/admin/neb/neb.component.html @@ -0,0 +1,52 @@ +
+ +
+
+ +
+

go-neb is a multi-purpose bot that can + provide various services, such as reaction GIFs and Github notifications. There are two options for + go-neb support in Dimension: using matrix.org's or self-hosting it. Each go-neb instance can have + multiple services associated with it (ie: one go-neb here can do everything).

+ + + + + + + + + + + + + + + + + + + +
NameEnabled BotsActions
No go-neb configurations.
+ {{ neb.upstreamId ? "matrix.org's go-neb" : "Self-hosted go-neb" }} + ({{ neb.adminUrl }}) + + {{ getEnabledBotsString(neb) }} + + + + + + + +
+ + +
+
+
\ No newline at end of file diff --git a/web/app/admin/neb/neb.component.scss b/web/app/admin/neb/neb.component.scss new file mode 100644 index 0000000..847d207 --- /dev/null +++ b/web/app/admin/neb/neb.component.scss @@ -0,0 +1,8 @@ +tr td:last-child { + vertical-align: middle; +} + +.appsvcConfigButton, +.editButton { + cursor: pointer; +} \ No newline at end of file diff --git a/web/app/admin/neb/neb.component.ts b/web/app/admin/neb/neb.component.ts new file mode 100644 index 0000000..d2fa73d --- /dev/null +++ b/web/app/admin/neb/neb.component.ts @@ -0,0 +1,123 @@ +import { Component } from "@angular/core"; +import { ToasterService } from "angular2-toaster"; +import { AdminNebApiService } from "../../shared/services/admin/admin-neb-api.service"; +import { AdminUpstreamApiService } from "../../shared/services/admin/admin-upstream-api.service"; +import { AdminAppserviceApiService } from "../../shared/services/admin/admin-appservice-api.service"; +import { FE_Appservice, FE_NebConfiguration, FE_Upstream } from "../../shared/models/admin-responses"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + AdminNebAppserviceConfigComponent, + AppserviceConfigDialogContext +} from "./appservice-config/appservice-config.component"; +import { Modal, overlayConfigFactory } from "ngx-modialog"; + +@Component({ + templateUrl: "./neb.component.html", + styleUrls: ["./neb.component.scss"], +}) +export class AdminNebComponent { + + public isLoading = true; + public isAddingModularNeb = false; + public hasModularNeb = false; + public upstreams: FE_Upstream[]; + public appservices: FE_Appservice[]; + public configurations: FE_NebConfiguration[]; + + constructor(private nebApi: AdminNebApiService, + private upstreamApi: AdminUpstreamApiService, + private appserviceApi: AdminAppserviceApiService, + private toaster: ToasterService, + private router: Router, + private activatedRoute: ActivatedRoute, + private modal: Modal) { + + this.reload().then(() => this.isLoading = false).catch(error => { + console.error(error); + this.toaster.pop("error", "Error loading go-neb configuration"); + }); + } + + private reload(): Promise { + return Promise.all([ + this.loadAppservices(), + this.loadConfigurations(), + this.loadUpstreams(), + ]); + } + + private loadUpstreams(): Promise { + return this.upstreamApi.getUpstreams().then(upstreams => { + this.upstreams = upstreams; + }); + } + + private loadAppservices(): Promise { + return this.appserviceApi.getAppservices().then(appservices => { + this.appservices = appservices; + }); + } + + private loadConfigurations(): Promise { + return this.nebApi.getConfigurations().then(nebConfigs => { + this.configurations = nebConfigs; + this.isLoading = false; + + this.hasModularNeb = false; + for (const neb of this.configurations) { + if (neb.upstreamId) { + this.hasModularNeb = true; + break; + } + } + }); + } + + public showAppserviceConfig(neb: FE_NebConfiguration) { + this.modal.open(AdminNebAppserviceConfigComponent, overlayConfigFactory({ + neb: neb, + + isBlocking: true, + size: 'lg', + }, AppserviceConfigDialogContext)); + } + + public getEnabledBotsString(neb: FE_NebConfiguration): string { + const result = neb.integrations.filter(i => i.isEnabled).map(i => i.displayName).join(", "); + if (!result) return "None"; + return result; + } + + public addSelfHostedNeb() { + this.router.navigate(["new", "selfhosted"], {relativeTo: this.activatedRoute}); + } + + public addModularHostedNeb() { + this.isAddingModularNeb = true; + const createNeb = (upstream: FE_Upstream) => { + return this.nebApi.newUpstreamConfiguration(upstream).then(neb => { + this.configurations.push(neb); + this.toaster.pop("success", "matrix.org's go-neb added", "Click the pencil icon to enable the bots."); + this.isAddingModularNeb = false; + this.hasModularNeb = true; + }).catch(error => { + console.error(error); + this.isAddingModularNeb = false; + this.toaster.pop("error", "Error adding matrix.org's go-neb"); + }); + }; + + const vectorUpstreams = this.upstreams.filter(u => u.type === "vector"); + if (vectorUpstreams.length === 0) { + console.log("Creating default scalar upstream"); + const scalarUrl = "https://scalar.vector.im/api"; + this.upstreamApi.newUpstream("modular", "vector", scalarUrl, scalarUrl).then(upstream => { + this.upstreams.push(upstream); + createNeb(upstream); + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Error creating matrix.org go-neb"); + }); + } else createNeb(vectorUpstreams[0]); + } +} diff --git a/web/app/admin/widgets/config-dialog.scss b/web/app/admin/widgets/config-dialog.scss new file mode 100644 index 0000000..0cb632f --- /dev/null +++ b/web/app/admin/widgets/config-dialog.scss @@ -0,0 +1,3 @@ +.label-block { + margin-bottom: 15px; +} \ No newline at end of file diff --git a/web/app/admin/widgets/etherpad/etherpad.component.html b/web/app/admin/widgets/etherpad/etherpad.component.html new file mode 100644 index 0000000..4bf7ec7 --- /dev/null +++ b/web/app/admin/widgets/etherpad/etherpad.component.html @@ -0,0 +1,22 @@ +
+
+

Etherpad Widget Configuration

+
+
+ +
+ +
\ No newline at end of file diff --git a/web/app/admin/widgets/etherpad/etherpad.component.scss b/web/app/admin/widgets/etherpad/etherpad.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/admin/widgets/etherpad/etherpad.component.ts b/web/app/admin/widgets/etherpad/etherpad.component.ts new file mode 100644 index 0000000..87f63dc --- /dev/null +++ b/web/app/admin/widgets/etherpad/etherpad.component.ts @@ -0,0 +1,35 @@ +import { Component } from "@angular/core"; +import { FE_EtherpadWidget } from "../../../shared/models/integration"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { WidgetConfigDialogContext } from "../widgets.component"; +import { AdminIntegrationsApiService } from "../../../shared/services/admin/admin-integrations-api.service"; + +@Component({ + templateUrl: "./etherpad.component.html", + styleUrls: ["./etherpad.component.scss", "../config-dialog.scss"], +}) +export class AdminWidgetEtherpadConfigComponent implements ModalComponent { + + public isUpdating = false; + public widget: FE_EtherpadWidget; + private originalWidget: FE_EtherpadWidget; + + constructor(public dialog: DialogRef, private adminIntegrationsApi: AdminIntegrationsApiService, private toaster: ToasterService) { + this.originalWidget = dialog.context.widget; + this.widget = JSON.parse(JSON.stringify(this.originalWidget)); + } + + public save() { + this.isUpdating = true; + this.adminIntegrationsApi.setIntegrationOptions(this.widget.category, this.widget.type, this.widget.options).then(() => { + this.originalWidget.options = this.widget.options; + this.toaster.pop("success", "Widget updated"); + this.dialog.close(); + }).catch(err => { + this.isUpdating = false; + console.error(err); + this.toaster.pop("error", "Error updating widget"); + }); + } +} diff --git a/web/app/admin/widgets/jitsi/jitsi.component.html b/web/app/admin/widgets/jitsi/jitsi.component.html new file mode 100644 index 0000000..43f7978 --- /dev/null +++ b/web/app/admin/widgets/jitsi/jitsi.component.html @@ -0,0 +1,29 @@ +
+
+

Jitsi Widget Configuration

+
+
+ + +
+ +
\ No newline at end of file diff --git a/web/app/admin/widgets/jitsi/jitsi.component.scss b/web/app/admin/widgets/jitsi/jitsi.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/admin/widgets/jitsi/jitsi.component.ts b/web/app/admin/widgets/jitsi/jitsi.component.ts new file mode 100644 index 0000000..68ebfbd --- /dev/null +++ b/web/app/admin/widgets/jitsi/jitsi.component.ts @@ -0,0 +1,35 @@ +import { Component } from "@angular/core"; +import { FE_JitsiWidget } from "../../../shared/models/integration"; +import { ToasterService } from "angular2-toaster"; +import { DialogRef, ModalComponent } from "ngx-modialog"; +import { WidgetConfigDialogContext } from "../widgets.component"; +import { AdminIntegrationsApiService } from "../../../shared/services/admin/admin-integrations-api.service"; + +@Component({ + templateUrl: "./jitsi.component.html", + styleUrls: ["./jitsi.component.scss", "../config-dialog.scss"], +}) +export class AdminWidgetJitsiConfigComponent implements ModalComponent { + + public isUpdating = false; + public widget: FE_JitsiWidget; + private originalWidget: FE_JitsiWidget; + + constructor(public dialog: DialogRef, private adminIntegrationsApi: AdminIntegrationsApiService, private toaster: ToasterService) { + this.originalWidget = dialog.context.widget; + this.widget = JSON.parse(JSON.stringify(this.originalWidget)); + } + + public save() { + this.isUpdating = true; + this.adminIntegrationsApi.setIntegrationOptions(this.widget.category, this.widget.type, this.widget.options).then(() => { + this.originalWidget.options = this.widget.options; + this.toaster.pop("success", "Widget updated"); + this.dialog.close(); + }).catch(err => { + this.isUpdating = false; + console.error(err); + this.toaster.pop("error", "Error updating widget"); + }); + } +} diff --git a/web/app/admin/widgets/widgets.component.html b/web/app/admin/widgets/widgets.component.html new file mode 100644 index 0000000..ed5c11b --- /dev/null +++ b/web/app/admin/widgets/widgets.component.html @@ -0,0 +1,34 @@ +
+ +
+
+ +
+

Widgets are small webpages that can be embedded in a Matrix room. Here you can configure which widgets + Dimension will offer to users.

+ + + + + + + + + + + + + + + + +
NameDescriptionActions
{{ widget.displayName }}{{ widget.description }} + + + + +
+
+
+
\ No newline at end of file diff --git a/web/app/admin/widgets/widgets.component.scss b/web/app/admin/widgets/widgets.component.scss new file mode 100644 index 0000000..3a81310 --- /dev/null +++ b/web/app/admin/widgets/widgets.component.scss @@ -0,0 +1,8 @@ +.editButton { + cursor: pointer; + vertical-align: text-bottom; +} + +tr td:last-child { + vertical-align: middle; +} \ No newline at end of file diff --git a/web/app/admin/widgets/widgets.component.ts b/web/app/admin/widgets/widgets.component.ts new file mode 100644 index 0000000..5debd8e --- /dev/null +++ b/web/app/admin/widgets/widgets.component.ts @@ -0,0 +1,72 @@ +import { Component } from "@angular/core"; +import { FE_Widget } from "../../shared/models/integration"; +import { ToasterService } from "angular2-toaster"; +import { AdminWidgetEtherpadConfigComponent } from "./etherpad/etherpad.component"; +import { Modal, overlayConfigFactory } from "ngx-modialog"; +import { BSModalContext } from "ngx-modialog/plugins/bootstrap"; +import { AdminWidgetJitsiConfigComponent } from "./jitsi/jitsi.component"; +import { AdminIntegrationsApiService } from "../../shared/services/admin/admin-integrations-api.service"; + +export class WidgetConfigDialogContext extends BSModalContext { + public widget: FE_Widget; +} + +@Component({ + templateUrl: "./widgets.component.html", + styleUrls: ["./widgets.component.scss"], +}) +export class AdminWidgetsComponent { + + public isLoading = true; + public isUpdating = false; + public widgets: FE_Widget[]; + + constructor(private adminIntegrationsApi: AdminIntegrationsApiService, private toaster: ToasterService, private modal: Modal) { + this.adminIntegrationsApi.getAllWidgets().then(widgets => { + this.isLoading = false; + this.widgets = widgets; + }).catch(err => { + console.error(err); + this.toaster.pop("error", "Failed to load widgets"); + }); + } + + public toggleWidget(widget: FE_Widget) { + widget.isEnabled = !widget.isEnabled; + this.isUpdating = true; + this.adminIntegrationsApi.toggleIntegration(widget.category, widget.type, widget.isEnabled).then(() => { + this.isUpdating = false; + this.toaster.pop("success", "Widget updated"); + }).catch(err => { + console.error(err); + widget.isEnabled = !widget.isEnabled; // revert change + this.isUpdating = false; + this.toaster.pop("error", "Error updating widget"); + }); + } + + public editWidget(widget: FE_Widget) { + let component = null; + + if (widget.type === "etherpad") component = AdminWidgetEtherpadConfigComponent; + if (widget.type === "jitsi") component = AdminWidgetJitsiConfigComponent; + + if (!component) { + console.error("No known dialog component for " + widget.type); + this.toaster.pop("error", "Error opening configuration page"); + return; + } + + this.modal.open(component, overlayConfigFactory({ + widget: widget, + + isBlocking: true, + size: 'lg', + }, WidgetConfigDialogContext)); + } + + public hasConfiguration(widget: FE_Widget) { + // Currently only Jitsi and Etherpad have additional configuration + return widget.type === "jitsi" || widget.type === "etherpad"; + } +} diff --git a/web/app/app.animations.ts b/web/app/app.animations.ts new file mode 100644 index 0000000..26ff967 --- /dev/null +++ b/web/app/app.animations.ts @@ -0,0 +1,13 @@ +import { animate, style, transition, trigger } from "@angular/animations"; + +export const ANIMATION_FADE_IN_NOT_OUT = + trigger('fadeInNotOutAnimation', [ + transition(':enter', [ + style({opacity: 0}), + animate('200ms', style({opacity: 1})), + ]), + // transition(':leave', [ + // style({opacity: 1}), + // animate('100ms', style({opacity: 0})), + // ]), + ]); \ No newline at end of file diff --git a/web/app/app.component.html b/web/app/app.component.html index 27c14e7..80510b9 100644 --- a/web/app/app.component.html +++ b/web/app/app.component.html @@ -2,6 +2,8 @@
+ +