Repository: mattermost/mattermost-plugin-github
Pull request #658 (2023-03-20 07:42:08)

[GH-548]:Fixed issue #548 on github 'org lock for username not working' 
**Summary** 

- When the Organization lock is set to a user's workspace rather than an organization, the create and attach modal will not show any results, so fix that by getting the data for the username as well.

- [Issue #548](https://github.com/mattermost/mattermost-plugin-github/issues/548) 


mattermost-build: Hello @Kshitij-Katiyar,

Thanks for your pull request! A Core Committer will review your pull request soon. For code contributions, you can learn more about the review process [here](https://developers.mattermost.com/contribute/getting-started/code-review/).

Patch:
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
new file mode 100644
index 0000000..cc876c0
--- /dev/null
+++ b/.github/workflows/playwright.yml
@@ -0,0 +1,163 @@
+name: Playwright Tests
+on:
+  schedule:
+    - cron: "0 0 * * *"
+  pull_request:
+  push:
+    branches:
+      - master
+
+env:
+  TERM: xterm
+  GO_VERSION: 1.19.6
+  NODE_VERSION: 16.15.0
+
+jobs:
+  playwright-run:
+    runs-on: ubuntu-latest
+    services:
+      postgres:
+        image: postgres:11.13
+        env:
+          POSTGRES_USER: mmuser
+          POSTGRES_PASSWORD: mostest
+          POSTGRES_DB: mattermost_test
+          POSTGRES_HOST_AUTH_METHOD: trust
+        ports:
+          - 5432:5432
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+      minio:
+        image: minio/minio:RELEASE.2019-10-11T00-38-09Z
+        env:
+          MINIO_ACCESS_KEY: minioaccesskey
+          MINIO_SECRET_KEY: miniosecretkey
+          MINIO_SSE_MASTER_KEY: "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574"
+      inbucket:
+        image: mattermost/inbucket:release-1.2.0
+        ports:
+          - 10080:10080
+          - 10110:10110
+          - 10025:10025
+      elasticsearch:
+        image: mattermost/mattermost-elasticsearch-docker:7.0.0
+        env:
+          http.host: "0.0.0.0"
+          http.port: 9200
+          http.cors.enabled: "true"
+          http.cors.allow-origin: "http://localhost:1358,http://127.0.0.1:1358"
+          http.cors.allow-headers: "X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization"
+          http.cors.allow-credentials: "true"
+          transport.host: "127.0.0.1"
+          ES_JAVA_OPTS: "-Xms512m -Xmx512m"
+        ports:
+          - 9200:9200
+      mattermost-server:
+        image: mattermost/mattermost-enterprise-edition:release-7.7
+        env:
+          DB_HOST: postgres
+          DB_PORT_NUMBER: 5432
+          MM_DBNAME: mattermost_test
+          MM_USERNAME: mmuser
+          MM_PASSWORD: mostest
+          CI_INBUCKET_HOST: inbucket
+          CI_INBUCKET_PORT: 10080
+          CI_MINIO_HOST: minio
+          IS_CI: true
+          MM_CLUSTERSETTINGS_READONLYCONFIG: false
+          MM_EMAILSETTINGS_SMTPSERVER: inbucket
+          MM_EMAILSETTINGS_SMTPPORT: 10025
+          MM_ELASTICSEARCHSETTINGS_CONNECTIONURL: http://elasticsearch:9200
+          MM_EXPERIMENTALSETTINGS_USENEWSAMLLIBRARY: true
+          MM_SQLSETTINGS_DATASOURCE: "postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10"
+          MM_SQLSETTINGS_DRIVERNAME: postgres
+          MM_PLUGINSETTINGS_ENABLEUPLOADS: true
+          MM_SERVICESETTINGS_SITEURL: http://localhost:8065
+          MM_PLUGINSETTINGS_AUTOMATICPREPACKAGEDPLUGINS: false
+          MM_ANNOUNCEMENTSETTINGS_ADMINNOTICESENABLED: false
+          MM_FEATUREFLAGS_AppsEnabled: true
+
+        ports:
+          - 8065:8065
+          - 4000:4000
+    env:
+      TYPE: NONE
+      PULL_REQUEST: ""
+      HEADLESS: true
+      DASHBOARD_ENABLE: false
+      FULL_REPORT: false
+      MM_SERVICESETTINGS_SITEURL: http://localhost:8065
+      MM_ADMIN_EMAIL: sysadmin@sample.mattermost.com
+      MM_ADMIN_USERNAME: sysadmin
+      MM_ADMIN_PASSWORD: Sys@dmin-sample1
+      TEST_DATABASE_URL: postgres://mmuser:mostest@localhost:5432/mattermost_test
+      MM_SERVICESETTINGS_ENABLEDEVELOPER: true
+    steps:
+      - name: ci/checkout-repo
+        uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+
+      - name: ci/setup-go
+        uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
+        with:
+          go-version: "${{ env.GO_VERSION }}"
+          cache: true
+
+      - name: ci/setup-node
+        uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
+        with:
+          node-version-file: ".nvmrc"
+          cache: "npm"
+          cache-dependency-path: webapp/package-lock.json
+
+      - name: ci/create-admin-user
+        run: |
+          STATUSCODE=$(curl -X POST -H "Content-Type: application/json" -d '{"email": "'${MM_ADMIN_EMAIL}'", "username": "'${MM_ADMIN_USERNAME}'", "password": "'${MM_ADMIN_PASSWORD}'"}' ${MM_SERVICESETTINGS_SITEURL}/api/v4/users -w "%{http_code}" -o /dev/stderr)
+          if test $STATUSCODE -ne 201; then exit 1; fi
+
+      - name: ci/install-go-dependencies
+        run: go mod tidy
+      - name: ci/install-npm-dependencies
+        run: cd webapp && npm ci
+
+      - name: ci/build-and-install-plugin
+        run: make deploy
+        env:
+          PLUGIN_E2E_MOCK_OAUTH_SERVER_URL: http://172.17.0.1:8080
+
+      - name: ci/setup-node
+        uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
+        with:
+          node-version: "16"
+          cache: "npm"
+          cache-dependency-path: e2e/playwright/package-lock.json
+
+      - name: ci/install-tests-deps
+        run: |
+          cd e2e/playwright
+          ./setup-environment
+
+      - name: ci/run-playwright-tests
+        run: |
+          cd e2e/playwright
+          npm run test-ci
+        env:
+          PLUGIN_E2E_MOCK_OAUTH_TOKEN: ${{ secrets.MM_GITHUB_PLUGIN_E2E_TOKEN }}
+          PW_BASE_URL: ${{ env.MM_SERVICESETTINGS_SITEURL }}
+
+      - name: ci/move-artifacts
+        if: success() || failure()
+        run: |
+          cd e2e/playwright
+          mkdir results
+
+          mv screenshots results
+          mv playwright-report results
+
+      - uses: actions/upload-artifact@v3
+        if: success() || failure()
+        with:
+          name: test-results
+          path: e2e/playwright/results
diff --git a/.gitignore b/.gitignore
index 16f09f6..29d47ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+node_modules
 dist/
 
 # Mac
diff --git a/Makefile b/Makefile
index e696390..add646e 100644
--- a/Makefile
+++ b/Makefile
@@ -31,6 +31,12 @@ ifneq ($(wildcard build/custom.mk),)
 	include build/custom.mk
 endif
 
+ifneq ($(MM_DEBUG),)
+	GO_BUILD_GCFLAGS = -gcflags "all=-N -l"
+else
+	GO_BUILD_GCFLAGS =
+endif
+
 ## Checks the code style, tests, builds and bundles the plugin.
 .PHONY: all
 all: check-style test dist
@@ -59,30 +65,19 @@ endif
 .PHONY: server
 server:
 ifneq ($(HAS_SERVER),)
-	mkdir -p server/dist;
-ifeq ($(MM_DEBUG),)
-ifneq ($(MM_SERVICESETTINGS_ENABLEDEVELOPER),)
-	@echo Building plugin only for $(DEFAULT_GOOS)-$(DEFAULT_GOARCH) because MM_SERVICESETTINGS_ENABLEDEVELOPER is enabled
-	cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH);
-else
-	cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-linux-amd64;
-	cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-linux-arm64;
-	cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-darwin-amd64;
-	cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-darwin-arm64;
-	cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-windows-amd64.exe;
-endif
-else
+ifneq ($(MM_DEBUG),)
 	$(info DEBUG mode is on; to disable, unset MM_DEBUG)
+endif
+	mkdir -p server/dist;
 ifneq ($(MM_SERVICESETTINGS_ENABLEDEVELOPER),)
 	@echo Building plugin only for $(DEFAULT_GOOS)-$(DEFAULT_GOARCH) because MM_SERVICESETTINGS_ENABLEDEVELOPER is enabled
-	cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH);
+	cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH);
 else
-	cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-linux-amd64;
-	cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-linux-arm64;
-	cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-darwin-amd64;
-	cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-darwin-arm64;
-	cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-windows-amd64.exe;
-endif
+	cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-amd64;
+	cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-linux-arm64;
+	cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-amd64;
+	cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-darwin-arm64;
+	cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) $(GO_BUILD_GCFLAGS) -trimpath -o dist/plugin-windows-amd64.exe;
 endif
 endif
 
diff --git a/README.md b/README.md
index d54819d..4e45e84 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ This repository is licensed under the [Apache 2.0 License](https://github.com/ma
 
 ## About the GitHub Plugin
 
-The Mattermost GitHub plugin uses a webhook to connect your GitHub account to Mattermost to listen for incoming GitHub events. Events notifications are via DM in Mattermost. The Events don’t need separate configuration. 
+The Mattermost GitHub plugin uses a webhook to connect your GitHub account to Mattermost to listen for incoming GitHub events. Events notifications are via DM in Mattermost. The Events don’t need separate configuration.
 
 After a System Admin has configured the GitHub plugin, run `/github connect` in a Mattermost channel to connect your Mattermost and GitHub accounts.
 
@@ -57,7 +57,7 @@ This guide assumes:
 
 ## Configuration
 
-GitHub plugin configuration starts by registering an OAuth app in GitHub and ends in Mattermost. 
+GitHub plugin configuration starts by registering an OAuth app in GitHub and ends in Mattermost.
 
 **Note:** If you're using GitHub Enterprise, replace all GitHub links below with your GitHub Enterprise URL.
 
@@ -69,7 +69,7 @@ You must first register the Mattermost GitHub Plugin as an authorized OAuth app
 2. Set the following values:
    - **Application name:** `Mattermost GitHub Plugin - <your company name>`
    - **Homepage URL:** `https://github.com/mattermost/mattermost-plugin-github`
-   - **Authorization callback URL:** `https://your-mattermost-url.com/plugins/github/oauth/complete`, replacing `https://your-mattermost-url.com` with your Mattermost URL. This value needs to match the Mattermost server URL that you or your users users log in to. 
+   - **Authorization callback URL:** `https://your-mattermost-url.com/plugins/github/oauth/complete`, replacing `https://your-mattermost-url.com` with your Mattermost URL. This value needs to match the Mattermost server URL that you or your users users log in to.
 3. Submit.
 4. Click **Generate a new client secret** and provide your GitHub password to continue.
 5. Copy the **Client ID** and **Client Secret** in the resulting screen.
@@ -108,13 +108,13 @@ To prevent this, either:
 
 or
 
-- If the user is an existing user account you want to preserve, change its username and restart the Mattermost server. Once restarted, the plugin will create a bot 
+- If the user is an existing user account you want to preserve, change its username and restart the Mattermost server. Once restarted, the plugin will create a bot
 account with the name `github`.
 
 **Note:** For `v0.9.0` and earlier of the GitHub plugin, instead of using bot accounts, set the username the plugin is attached to in **System Console > Plugins > GitHub**.
 
 #### Generate a Key
-  
+
 Open **System Console > Plugins > GitHub** and do the following:
 
 1. Generate a new value for **At Rest Encryption Key**.
@@ -134,7 +134,7 @@ Once configuration is complete, run the `/github connect` slash command from any
 
 When you’ve tested the plugin and confirmed it’s working, notify your team so they can connect their GitHub account to Mattermost and get started. Copy and paste the text below, edit it to suit your requirements, and send it out.
 
-> Hi team, 
+> Hi team,
 
 > We've set up the Mattermost GitHub plugin, so you can get notifications from GitHub in Mattermost. To get started, run the `/github connect` slash command from any channel within Mattermost to connect your Mattermost account with GitHub. Then, take a look at the [slash commands](#slash-commands) section for details about how to use the plugin.
 
@@ -150,9 +150,9 @@ When you’ve tested the plugin and confirmed it’s working, notify your team s
   - The following flags are supported:
      - `--features`: comma-delimited list of one or more of: issues, pulls, pulls_merged, pushes, creates, deletes, issue_creations, issue_comments, pull_reviews, label:"labelname". Defaults to pulls,issues,creates,deletes.
      - `--exclude-org-member`: events triggered by organization members will not be delivered. It will be locked to the organization provided in the plugin configuration and it will only work for users whose membership is public. Note that organization members and collaborators are not the same.
-     - `--render-style`: notifications will be delivered in the specified style (for example, the body of a pull request will not be displayed). Supported 
+     - `--render-style`: notifications will be delivered in the specified style (for example, the body of a pull request will not be displayed). Supported
      values are `collapsed`, `skip-body` or `default` (same as omitting the flag).
-   
+
 * __Get to do items__ - Use `/github todo` to get an ephemeral message with items to do in GitHub, including a list of unread messages and pull requests awaiting your review.
 * __Update settings__ - Use `/github settings` to update your settings for notifications and daily reminders.
 * __And more!__ - Run `/github help` to see what else the slash command can do.
@@ -182,3 +182,7 @@ GitHub user tokens are AES encrypted with an At Rest Encryption Key configured i
 ## Development
 
 This plugin contains both a server and web app portion. Read our documentation about the [Developer Workflow](https://developers.mattermost.com/extend/plugins/developer-workflow/) and [Developer Setup](https://developers.mattermost.com/extend/plugins/developer-setup/) for more information about developing and extending plugins.
+
+### Playwright e2e tests
+
+In order to get your environment set up to run [Playwright](https://playwright.dev) tests, please see the setup guide at [e2e/playwright](/e2e/playwright#readme).
diff --git a/build/custom.mk b/build/custom.mk
index 86bd34d..8891c8b 100644
--- a/build/custom.mk
+++ b/build/custom.mk
@@ -1,6 +1,13 @@
 # Include custom targets and environment variables here
 
-ifndef MM_RUDDER_WRITE_KEY
-	MM_RUDDER_WRITE_KEY = 1d5bMvdrfWClLxgK1FvV3s4U1tg
+# If there's no MM_RUDDER_PLUGINS_PROD, add DEV data
+RUDDER_WRITE_KEY = 1d5bMvdrfWClLxgK1FvV3s4U1tg
+ifdef MM_RUDDER_PLUGINS_PROD
+RUDDER_WRITE_KEY = $(MM_RUDDER_PLUGINS_PROD)
+endif
+
+GO_BUILD_FLAGS += -ldflags '-X "github.com/mattermost/mattermost-plugin-api/experimental/telemetry.rudderWriteKey=$(RUDDER_WRITE_KEY)"'
+
+ifdef PLUGIN_E2E_MOCK_OAUTH_SERVER_URL
+GO_BUILD_FLAGS += -ldflags '-X "github.com/mattermost/mattermost-plugin-github/server/plugin.testOAuthServerURL=$(PLUGIN_E2E_MOCK_OAUTH_SERVER_URL)"'
 endif
-GO_BUILD_FLAGS += -ldflags '-X "github.com/mattermost/mattermost-plugin-api/experimental/telemetry.rudderWriteKey=$(MM_RUDDER_WRITE_KEY)"'
diff --git a/go.mod b/go.mod
index ff78c47..c2aac4f 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/Masterminds/sprig/v3 v3.2.2
 	github.com/google/go-github/v41 v41.0.0
 	github.com/gorilla/mux v1.8.0
-	github.com/mattermost/mattermost-plugin-api v0.1.1
+	github.com/mattermost/mattermost-plugin-api v0.1.3-0.20230323124751-86c7be7ffbac
 	// mmgoget: github.com/mattermost/mattermost-server/v6@v7.5.0 is replaced by -> github.com/mattermost/mattermost-server/v6@21aec2741b
 	github.com/mattermost/mattermost-server/v6 v6.0.0-20221109191448-21aec2741bfe
 	github.com/microcosm-cc/bluemonday v1.0.19
diff --git a/go.sum b/go.sum
index f0fbbe7..e4f2757 100644
--- a/go.sum
+++ b/go.sum
@@ -231,8 +231,8 @@ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ
 github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
 github.com/mattermost/logr/v2 v2.0.15 h1:+WNbGcsc3dBao65eXlceB6dTILNJRIrvubnsTl3zBew=
 github.com/mattermost/logr/v2 v2.0.15/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
-github.com/mattermost/mattermost-plugin-api v0.1.1 h1:bNnPbWCLWZpT/k2kjUxNnzCfUggU8WKs2ddz7hNjg1U=
-github.com/mattermost/mattermost-plugin-api v0.1.1/go.mod h1:9yZhtg0bBj3kqSTjXnjYBMZoTsWbe3ajdFMdl9/Jz34=
+github.com/mattermost/mattermost-plugin-api v0.1.3-0.20230323124751-86c7be7ffbac h1:fxdqYaw6wJfMSodMP0SNPm31r9kJ0NI8uQiWVpnj8F0=
+github.com/mattermost/mattermost-plugin-api v0.1.3-0.20230323124751-86c7be7ffbac/go.mod h1:EON5x6XoqubANT9zYLuL+m42SijzpK/dDOuu76PGbSc=
 github.com/mattermost/mattermost-server/v6 v6.0.0-20221109191448-21aec2741bfe h1:9P759/e7xB3IQYo+6Ju9qsj3YUxpnPRXl2x+XFFsRIM=
 github.com/mattermost/mattermost-server/v6 v6.0.0-20221109191448-21aec2741bfe/go.mod h1:Eu6nct2XZRPlHHW4MfZtjli5bY7G9QomKcor/lm+0Sw=
 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
diff --git a/server/plugin/api.go b/server/plugin/api.go
index 13690f2..33dc73a 100644
--- a/server/plugin/api.go
+++ b/server/plugin/api.go
@@ -408,6 +408,9 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter,
 		return
 	}
 
+	// track the successful connection
+	p.TrackUserEvent("account_connected", c.UserID, nil)
+
 	userInfo := &GitHubUserInfo{
 		UserID:         state.UserID,
 		Token:          tok,
@@ -1203,41 +1206,75 @@ func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Re
 	p.writeJSON(w, allMilestones)
 }
 
+func getRepositoryList(c context.Context, userName string, githubClient *github.Client, opt github.ListOptions) ([]*github.Repository, error) {
+	var allRepos []*github.Repository
+	for {
+		repos, resp, err := githubClient.Repositories.List(c, userName, &github.RepositoryListOptions{ListOptions: opt})
+		if err != nil {
+			return nil, err
+		}
+
+		allRepos = append(allRepos, repos...)
+		if resp.NextPage == 0 {
+			break
+		}
+
+		opt.Page = resp.NextPage
+	}
+
+	return allRepos, nil
+}
+
+func getRepositoryListByOrg(c context.Context, org string, githubClient *github.Client, opt github.ListOptions) ([]*github.Repository, int, error) {
+	var allRepos []*github.Repository
+	for {
+		repos, resp, err := githubClient.Repositories.ListByOrg(c, org, &github.RepositoryListByOrgOptions{Sort: "full_name", ListOptions: opt})
+		if err != nil {
+			return nil, resp.StatusCode, err
+		}
+
+		allRepos = append(allRepos, repos...)
+		if resp.NextPage == 0 {
+			break
+		}
+		opt.Page = resp.NextPage
+	}
+
+	return allRepos, http.StatusOK, nil
+}
+
 func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http.Request) {
 	githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo)
 
 	org := p.getConfiguration().GitHubOrg
 
 	var allRepos []*github.Repository
+	var err error
+	var statusCode int
 	opt := github.ListOptions{PerPage: 50}
 
 	if org == "" {
-		for {
-			repos, resp, err := githubClient.Repositories.List(c.Ctx, "", &github.RepositoryListOptions{ListOptions: opt})
-			if err != nil {
-				c.Log.WithError(err).Warnf("Failed to list repositories")
-				p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
-				return
-			}
-			allRepos = append(allRepos, repos...)
-			if resp.NextPage == 0 {
-				break
-			}
-			opt.Page = resp.NextPage
+		allRepos, err = getRepositoryList(c.Ctx, "", githubClient, opt)
+		if err != nil {
+			c.Log.WithError(err).Warnf("Failed to list repositories")
+			p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
+			return
 		}
 	} else {
-		for {
-			repos, resp, err := githubClient.Repositories.ListByOrg(c.Ctx, org, &github.RepositoryListByOrgOptions{Sort: "full_name", ListOptions: opt})
-			if err != nil {
-				c.Log.WithError(err).Warnf("Failed to list repositories by org")
+		allRepos, statusCode, err = getRepositoryListByOrg(c.Ctx, org, githubClient, opt)
+		if err != nil {
+			if statusCode == http.StatusNotFound {
+				allRepos, err = getRepositoryList(c.Ctx, org, githubClient, opt)
+				if err != nil {
+					c.Log.WithError(err).Warnf("Failed to list repositories")
+					p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
+					return
+				}
+			} else {
+				c.Log.WithError(err).Warnf("Failed to list repositories")
 				p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError})
 				return
 			}
-			allRepos = append(allRepos, repos...)
-			if resp.NextPage == 0 {
-				break
-			}
-			opt.Page = resp.NextPage
 		}
 	}
 
diff --git a/server/plugin/configuration.go b/server/plugin/configuration.go
index 7aca982..900275f 100644
--- a/server/plugin/configuration.go
+++ b/server/plugin/configuration.go
@@ -212,16 +212,11 @@ func (p *Plugin) OnConfigurationChange() error {
 	if err != nil {
 		return errors.Wrap(err, "failed to register command")
 	}
-
-	enableDiagnostics := false
-	if config := p.API.GetConfig(); config != nil {
-		if configValue := config.LogSettings.EnableDiagnostics; configValue != nil {
-			enableDiagnostics = *configValue
-		}
+	// Some config changes require reloading tracking config
+	if p.tracker != nil {
+		p.tracker.ReloadConfig(telemetry.NewTrackerConfig(p.API.GetConfig()))
 	}
 
-	p.tracker = telemetry.NewTracker(p.telemetryClient, p.API.GetDiagnosticId(), p.API.GetServerVersion(), Manifest.Id, Manifest.Version, "github", enableDiagnostics)
-
 	return nil
 }
 
diff --git a/server/plugin/flows.go b/server/plugin/flows.go
index d5c7454..c480401 100644
--- a/server/plugin/flows.go
+++ b/server/plugin/flows.go
@@ -12,7 +12,6 @@ import (
 	pluginapi "github.com/mattermost/mattermost-plugin-api"
 	"github.com/mattermost/mattermost-plugin-api/experimental/flow"
 
-	"github.com/mattermost/mattermost-plugin-api/experimental/telemetry"
 	"github.com/pkg/errors"
 
 	"github.com/mattermost/mattermost-server/v6/model"
@@ -23,6 +22,11 @@ type PingBroker interface {
 	SubscribePings() <-chan *github.PingEvent
 }
 
+type Tracker interface {
+	TrackEvent(event string, properties map[string]interface{})
+	TrackUserEvent(event, userID string, properties map[string]interface{})
+}
+
 type FlowManager struct {
 	client           *pluginapi.Client
 	pluginURL        string
@@ -32,7 +36,7 @@ type FlowManager struct {
 	getGitHubClient  func(ctx context.Context, userID string) (*github.Client, error)
 
 	pingBroker PingBroker
-	tracker    telemetry.Tracker
+	tracker    Tracker
 
 	setupFlow        *flow.Flow
 	oauthFlow        *flow.Flow
@@ -50,7 +54,7 @@ func (p *Plugin) NewFlowManager() *FlowManager {
 		getGitHubClient:  p.GetGitHubClient,
 
 		pingBroker: p.webhookBroker,
-		tracker:    p.tracker,
+		tracker:    p,
 	}
 
 	fm.setupFlow = fm.newFlow("setup").WithSteps(
@@ -223,14 +227,14 @@ func (fm *FlowManager) StartSetupWizard(userID string, delegatedFrom string) err
 }
 
 func (fm *FlowManager) trackStartSetupWizard(userID string, fromInvite bool) {
-	_ = fm.tracker.TrackUserEvent("setup_wizard_start", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("setup_wizard_start", userID, map[string]interface{}{
 		"from_invite": fromInvite,
 		"time":        model.GetMillis(),
 	})
 }
 
 func (fm *FlowManager) trackCompleteSetupWizard(userID string) {
-	_ = fm.tracker.TrackUserEvent("setup_wizard_complete", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("setup_wizard_complete", userID, map[string]interface{}{
 		"time": model.GetMillis(),
 	})
 }
@@ -249,13 +253,13 @@ func (fm *FlowManager) StartOauthWizard(userID string) error {
 }
 
 func (fm *FlowManager) trackStartOauthWizard(userID string) {
-	_ = fm.tracker.TrackUserEvent("oauth_wizard_start", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("oauth_wizard_start", userID, map[string]interface{}{
 		"time": model.GetMillis(),
 	})
 }
 
 func (fm *FlowManager) trackCompleteOauthWizard(userID string) {
-	_ = fm.tracker.TrackUserEvent("oauth_wizard_complete", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("oauth_wizard_complete", userID, map[string]interface{}{
 		"time": model.GetMillis(),
 	})
 }
@@ -591,13 +595,13 @@ func (fm *FlowManager) StartWebhookWizard(userID string) error {
 }
 
 func (fm *FlowManager) trackStartWebhookWizard(userID string) {
-	_ = fm.tracker.TrackUserEvent("webhook_wizard_start", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("webhook_wizard_start", userID, map[string]interface{}{
 		"time": model.GetMillis(),
 	})
 }
 
 func (fm *FlowManager) trackCompleteWebhookWizard(userID string) {
-	_ = fm.tracker.TrackUserEvent("webhook_wizard_complete", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("webhook_wizard_complete", userID, map[string]interface{}{
 		"time": model.GetMillis(),
 	})
 }
@@ -755,13 +759,13 @@ func (fm *FlowManager) StartAnnouncementWizard(userID string) error {
 }
 
 func (fm *FlowManager) trackStartAnnouncementWizard(userID string) {
-	_ = fm.tracker.TrackUserEvent("announcement_wizard_start", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("announcement_wizard_start", userID, map[string]interface{}{
 		"time": model.GetMillis(),
 	})
 }
 
 func (fm *FlowManager) trackCompletAnnouncementWizard(userID string) {
-	_ = fm.tracker.TrackUserEvent("announcement_wizard_complete", userID, map[string]interface{}{
+	fm.tracker.TrackUserEvent("announcement_wizard_complete", userID, map[string]interface{}{
 		"time": model.GetMillis(),
 	})
 }
diff --git a/server/plugin/permalinks.go b/server/plugin/permalinks.go
index 84d97cc..e0cc3a0 100644
--- a/server/plugin/permalinks.go
+++ b/server/plugin/permalinks.go
@@ -3,6 +3,7 @@ package plugin
 import (
 	"context"
 	"encoding/hex"
+	"path"
 	"strings"
 	"time"
 
@@ -75,7 +76,7 @@ func (p *Plugin) getReplacements(msg string) []replacement {
 			case "commit":
 				r.permalinkInfo.commit = m[j]
 			case "path":
-				r.permalinkInfo.path = m[j]
+				r.permalinkInfo.path = strings.TrimPrefix(path.Join("/", m[j]), "/")
 			case "line":
 				r.permalinkInfo.line = m[j]
 			}
diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go
index b239a70..5e8414d 100644
--- a/server/plugin/plugin.go
+++ b/server/plugin/plugin.go
@@ -58,6 +58,10 @@ const (
 
 var (
 	Manifest model.Manifest = root.Manifest
+
+	// testOAuthServerURL is the URL for the oauthServer used for testing purposes
+	// It should be set through ldflags when compiling for E2E, and keep it blank otherwise
+	testOAuthServerURL = ""
 )
 
 type Plugin struct {
@@ -237,11 +241,7 @@ func (p *Plugin) OnActivate() error {
 	}
 
 	p.initializeAPI()
-
-	p.telemetryClient, err = telemetry.NewRudderClient()
-	if err != nil {
-		p.API.LogWarn("Telemetry client not started", "error", err.Error())
-	}
+	p.initializeTelemetry()
 
 	p.webhookBroker = NewWebhookBroker(p.sendGitHubPingEvent)
 	p.oauthBroker = NewOAuthBroker(p.sendOAuthCompleteEvent)
@@ -274,7 +274,9 @@ func (p *Plugin) OnActivate() error {
 func (p *Plugin) OnDeactivate() error {
 	p.webhookBroker.Close()
 	p.oauthBroker.Close()
-
+	if err := p.telemetryClient.Close(); err != nil {
+		p.API.LogWarn("Telemetry client failed to close", "error", err.Error())
+	}
 	return nil
 }
 
@@ -512,6 +514,10 @@ func (p *Plugin) getOAuthConfig(privateAllowed bool) *oauth2.Config {
 	}
 
 	baseURL := config.getBaseURL()
+	if testOAuthServerURL != "" {
+		baseURL = testOAuthServerURL + "/"
+	}
+
 	authURL, _ := url.Parse(baseURL)
 	tokenURL, _ := url.Parse(baseURL)
 
diff --git a/server/plugin/telemetry.go b/server/plugin/telemetry.go
index 813951e..a094a7c 100644
--- a/server/plugin/telemetry.go
+++ b/server/plugin/telemetry.go
@@ -4,6 +4,8 @@ import (
 	"strings"
 
 	pluginapi "github.com/mattermost/mattermost-plugin-api"
+	"github.com/mattermost/mattermost-plugin-api/experimental/bot/logger"
+	"github.com/mattermost/mattermost-plugin-api/experimental/telemetry"
 	"github.com/pkg/errors"
 )
 
@@ -11,6 +13,20 @@ const (
 	keysPerPage = 1000
 )
 
+func (p *Plugin) TrackEvent(event string, properties map[string]interface{}) {
+	err := p.tracker.TrackEvent(event, properties)
+	if err != nil {
+		p.API.LogDebug("Error sending telemetry event", "event", event, "error", err.Error())
+	}
+}
+
+func (p *Plugin) TrackUserEvent(event, userID string, properties map[string]interface{}) {
+	err := p.tracker.TrackUserEvent(event, userID, properties)
+	if err != nil {
+		p.API.LogDebug("Error sending user telemetry event", "event", event, "error", err.Error())
+	}
+}
+
 func (p *Plugin) SendDailyTelemetry() {
 	config := p.getConfiguration()
 
@@ -19,7 +35,7 @@ func (p *Plugin) SendDailyTelemetry() {
 		p.API.LogWarn("Failed to get the number of connected users for telemetry", "error", err)
 	}
 
-	_ = p.tracker.TrackEvent("stats", map[string]interface{}{
+	p.TrackEvent("stats", map[string]interface{}{
 		"connected_user_count":          connectedUserCount,
 		"is_oauth_configured":           config.IsOAuthConfigured(),
 		"is_sass":                       config.IsSASS(),
@@ -53,3 +69,29 @@ func (p *Plugin) getConnectedUserCount() (int64, error) {
 
 	return count, nil
 }
+
+// Initialize telemetry setups the tracker/clients needed to send telemetry data.
+// The telemetry.NewTrackerConfig(...) param will take care of extract/parse the config to set rge right settings.
+// If you don't want the default behavior you still can pass a different telemetry.TrackerConfig data.
+func (p *Plugin) initializeTelemetry() {
+	var err error
+
+	// Telemetry client
+	p.telemetryClient, err = telemetry.NewRudderClient()
+	if err != nil {
+		p.API.LogWarn("Telemetry client not started", "error", err.Error())
+		return
+	}
+
+	// Get config values
+	p.tracker = telemetry.NewTracker(
+		p.telemetryClient,
+		p.API.GetDiagnosticId(),
+		p.API.GetServerVersion(),
+		Manifest.Id,
+		Manifest.Version,
+		"github",
+		telemetry.NewTrackerConfig(p.API.GetConfig()),
+		logger.New(p.API),
+	)
+}
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index 0baf792..b72203d 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -1248,24 +1248,6 @@
         "regenerator-runtime": "^0.13.4"
       }
     },
-    "@babel/runtime-corejs2": {
-      "version": "7.11.2",
-      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.11.2.tgz",
-      "integrity": "sha512-AC/ciV28adSSpEkBglONBWq4/Lvm6GAZuxIoyVtsnUpZMl0bxLtoChEnYAkP+47KyOCayZanojtflUEUJtR/6Q==",
-      "dev": true,
-      "requires": {
-        "core-js": "^2.6.5",
-        "regenerator-runtime": "^0.13.4"
-      },
-      "dependencies": {
-        "core-js": {
-          "version": "2.6.11",
-          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
-          "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==",
-          "dev": true
-        }
-      }
-    },
     "@babel/template": {
       "version": "7.10.4",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
@@ -1378,64 +1360,6 @@
         "babel-plugin-emotion": "^10.0.27"
       }
     },
-    "@emotion/babel-utils": {
-      "version": "0.6.10",
-      "resolved": "https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.10.tgz",
-      "integrity": "sha512-/fnkM/LTEp3jKe++T0KyTszVGWNKPNOUJfjNKLO17BzQ6QPxgbg3whayom1Qr2oLFH3V92tDymU+dT5q676uow==",
-      "dev": true,
-      "requires": {
-        "@emotion/hash": "^0.6.6",
-        "@emotion/memoize": "^0.6.6",
-        "@emotion/serialize": "^0.9.1",
-        "convert-source-map": "^1.5.1",
-        "find-root": "^1.1.0",
-        "source-map": "^0.7.2"
-      },
-      "dependencies": {
-        "@emotion/hash": {
-          "version": "0.6.6",
-          "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz",
-          "integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ==",
-          "dev": true
-        },
-        "@emotion/memoize": {
-          "version": "0.6.6",
-          "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz",
-          "integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==",
-          "dev": true
-        },
-        "@emotion/serialize": {
-          "version": "0.9.1",
-          "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.9.1.tgz",
-          "integrity": "sha512-zTuAFtyPvCctHBEL8KZ5lJuwBanGSutFEncqLn/m9T1a6a93smBStK+bZzcNPgj4QS8Rkw9VTwJGhRIUVO8zsQ==",
-          "dev": true,
-          "requires": {
-            "@emotion/hash": "^0.6.6",
-            "@emotion/memoize": "^0.6.6",
-            "@emotion/unitless": "^0.6.7",
-            "@emotion/utils": "^0.8.2"
-          }
-        },
-        "@emotion/unitless": {
-          "version": "0.6.7",
-          "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.7.tgz",
-          "integrity": "sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg==",
-          "dev": true
-        },
-        "@emotion/utils": {
-          "version": "0.8.2",
-          "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz",
-          "integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw==",
-          "dev": true
-        },
-        "source-map": {
-          "version": "0.7.3",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
-          "dev": true
-        }
-      }
-    },
     "@emotion/cache": {
       "version": "10.0.29",
       "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
@@ -1656,12 +1580,6 @@
       "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
       "dev": true
     },
-    "@icons/material": {
-      "version": "0.2.4",
-      "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
-      "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==",
-      "dev": true
-    },
     "@istanbuljs/load-nyc-config": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -2793,25 +2711,6 @@
         }
       }
     },
-    "@popmotion/easing": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/@popmotion/easing/-/easing-1.0.2.tgz",
-      "integrity": "sha512-IkdW0TNmRnWTeWI7aGQIVDbKXPWHVEYdGgd5ZR4SH/Ty/61p63jCjrPxX1XrR7IGkl08bjhJROStD7j+RKgoIw==",
-      "dev": true
-    },
-    "@popmotion/popcorn": {
-      "version": "0.4.4",
-      "resolved": "https://registry.npmjs.org/@popmotion/popcorn/-/popcorn-0.4.4.tgz",
-      "integrity": "sha512-jYO/8319fKoNLMlY4ZJPiPu8Ea8occYwRZhxpaNn/kZsK4QG2E7XFlXZMJBsTWDw7I1i0uaqyC4zn1nwEezLzg==",
-      "dev": true,
-      "requires": {
-        "@popmotion/easing": "^1.0.1",
-        "framesync": "^4.0.1",
-        "hey-listen": "^1.0.8",
-        "style-value-types": "^3.1.7",
-        "tslib": "^1.10.0"
-      }
-    },
     "@popperjs/core": {
       "version": "2.4.4",
       "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.4.tgz",
@@ -3859,12 +3758,6 @@
       "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
       "dev": true
     },
-    "asap": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
-      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
-      "dev": true
-    },
     "asn1.js": {
       "version": "4.10.1",
       "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
@@ -4295,6 +4188,16 @@
       "dev": true,
       "optional": true
     },
+    "bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
     "bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -4313,21 +4216,6 @@
       "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
       "dev": true
     },
-    "bootstrap": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
-      "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==",
-      "dev": true
-    },
-    "bootstrap-colorpicker": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/bootstrap-colorpicker/-/bootstrap-colorpicker-2.5.2.tgz",
-      "integrity": "sha512-krzBno9AMUwI2+IDwMvjnpqpa2f8womW0CCKmEcxGzVkolCFrt22jjMjzx1NZqB8C1DUdNgZP4LfyCsgpHRiYA==",
-      "dev": true,
-      "requires": {
-        "jquery": ">=1.10"
-      }
-    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -4596,16 +4484,6 @@
         "unset-value": "^1.0.0"
       }
     },
-    "call-bind": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
-      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-      "dev": true,
-      "requires": {
-        "function-bind": "^1.1.1",
-        "get-intrinsic": "^1.0.2"
-      }
-    },
     "callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4658,35 +4536,6 @@
       "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
       "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="
     },
-    "chart.js": {
-      "version": "2.8.0",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz",
-      "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==",
-      "dev": true,
-      "requires": {
-        "chartjs-color": "^2.1.0",
-        "moment": "^2.10.2"
-      }
-    },
-    "chartjs-color": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
-      "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
-      "dev": true,
-      "requires": {
-        "chartjs-color-string": "^0.6.0",
-        "color-convert": "^1.9.3"
-      }
-    },
-    "chartjs-color-string": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
-      "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
-      "dev": true,
-      "requires": {
-        "color-name": "^1.0.0"
-      }
-    },
     "cheerio": {
       "version": "1.0.0-rc.12",
       "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
@@ -5103,12 +4952,6 @@
       "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
       "dev": true
     },
-    "compass-mixins": {
-      "version": "0.12.10",
-      "resolved": "https://registry.npmjs.org/compass-mixins/-/compass-mixins-0.12.10.tgz",
-      "integrity": "sha1-zZ8V+CnE6WDMQ7sibwSbKL65nUE=",
-      "dev": true
-    },
     "component-emitter": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@@ -5165,12 +5008,6 @@
         "safe-buffer": "~5.1.1"
       }
     },
-    "cookiejar": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
-      "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
-      "dev": true
-    },
     "copy-concurrently": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@@ -5250,53 +5087,6 @@
         }
       }
     },
-    "create-emotion": {
-      "version": "9.2.12",
-      "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-9.2.12.tgz",
-      "integrity": "sha512-P57uOF9NL2y98Xrbl2OuiDQUZ30GVmASsv5fbsjF4Hlraip2kyAvMm+2PoYUvFFw03Fhgtxk3RqZSm2/qHL9hA==",
-      "dev": true,
-      "requires": {
-        "@emotion/hash": "^0.6.2",
-        "@emotion/memoize": "^0.6.1",
-        "@emotion/stylis": "^0.7.0",
-        "@emotion/unitless": "^0.6.2",
-        "csstype": "^2.5.2",
-        "stylis": "^3.5.0",
-        "stylis-rule-sheet": "^0.0.10"
-      },
-      "dependencies": {
-        "@emotion/hash": {
-          "version": "0.6.6",
-          "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz",
-          "integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ==",
-          "dev": true
-        },
-        "@emotion/memoize": {
-          "version": "0.6.6",
-          "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz",
-          "integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==",
-          "dev": true
-        },
-        "@emotion/stylis": {
-          "version": "0.7.1",
-          "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.7.1.tgz",
-          "integrity": "sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ==",
-          "dev": true
-        },
-        "@emotion/unitless": {
-          "version": "0.6.7",
-          "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.7.tgz",
-          "integrity": "sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg==",
-          "dev": true
-        },
-        "csstype": {
-          "version": "2.6.13",
-          "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz",
-          "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==",
-          "dev": true
-        }
-      }
-    },
     "create-hash": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
@@ -5324,27 +5114,6 @@
         "sha.js": "^2.4.8"
       }
     },
-    "create-react-context": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz",
-      "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==",
-      "dev": true,
-      "requires": {
-        "gud": "^1.0.0",
-        "warning": "^4.0.3"
-      },
-      "dependencies": {
-        "warning": {
-          "version": "4.0.3",
-          "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
-          "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
-          "dev": true,
-          "requires": {
-            "loose-envify": "^1.0.0"
-          }
-        }
-      }
-    },
     "cross-spawn": {
       "version": "6.0.5",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -5477,12 +5246,6 @@
         }
       }
     },
-    "css-vars-ponyfill": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.0.2.tgz",
-      "integrity": "sha512-x2q/VmPwQFxNOXdQcR+VOJ6GjaLDVo1aqJhZp2ommTwBa7Vlg5ulBcxknIOxvU/mkTOPhsafaXQE1oRnLJjErw==",
-      "dev": true
-    },
     "cssesc": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -5592,12 +5355,6 @@
       "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
       "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
-    },
     "deep-is": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -5768,43 +5525,12 @@
         "to-camel-case": "1.0.0"
       }
     },
-    "dom-helpers": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
-      "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.1.2"
-      }
-    },
-    "dom-serializer": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
-      "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
-      "dev": true,
-      "requires": {
-        "domelementtype": "^2.0.1",
-        "entities": "^2.0.0"
-      }
-    },
-    "dom-walk": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
-      "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
-      "dev": true
-    },
     "domain-browser": {
       "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": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
-      "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
-      "dev": true
-    },
     "domexception": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
@@ -5875,56 +5601,6 @@
       "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
       "dev": true
     },
-    "emotion": {
-      "version": "9.2.12",
-      "resolved": "https://registry.npmjs.org/emotion/-/emotion-9.2.12.tgz",
-      "integrity": "sha512-hcx7jppaI8VoXxIWEhxpDW7I+B4kq9RNzQLmsrF6LY8BGKqe2N+gFAQr0EfuFucFlPs2A9HM4+xNj4NeqEWIOQ==",
-      "dev": true,
-      "requires": {
-        "babel-plugin-emotion": "^9.2.11",
-        "create-emotion": "^9.2.12"
-      },
-      "dependencies": {
-        "@emotion/hash": {
-          "version": "0.6.6",
-          "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz",
-          "integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ==",
-          "dev": true
-        },
-        "@emotion/memoize": {
-          "version": "0.6.6",
-          "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz",
-          "integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==",
-          "dev": true
-        },
-        "@emotion/stylis": {
-          "version": "0.7.1",
-          "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.7.1.tgz",
-          "integrity": "sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ==",
-          "dev": true
-        },
-        "babel-plugin-emotion": {
-          "version": "9.2.11",
-          "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz",
-          "integrity": "sha512-dgCImifnOPPSeXod2znAmgc64NhaaOjGEHROR/M+lmStb3841yK1sgaDYAYMnlvWNz8GnpwIPN0VmNpbWYZ+VQ==",
-          "dev": true,
-          "requires": {
-            "@babel/helper-module-imports": "^7.0.0",
-            "@emotion/babel-utils": "^0.6.4",
-            "@emotion/hash": "^0.6.2",
-            "@emotion/memoize": "^0.6.1",
-            "@emotion/stylis": "^0.7.0",
-            "babel-plugin-macros": "^2.0.0",
-            "babel-plugin-syntax-jsx": "^6.18.0",
-            "convert-source-map": "^1.5.0",
-            "find-root": "^1.1.0",
-            "mkdirp": "^0.5.1",
-            "source-map": "^0.5.7",
-            "touch": "^2.0.1"
-          }
-        }
-      }
-    },
     "encoding": {
       "version": "0.1.12",
       "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
@@ -5962,12 +5638,6 @@
         "ansi-colors": "^4.1.1"
       }
     },
-    "entities": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
-      "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
-      "dev": true
-    },
     "env-paths": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@@ -6373,12 +6043,6 @@
         }
       }
     },
-    "eslint-plugin-header": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.0.0.tgz",
-      "integrity": "sha512-OIu2ciVW8jK4Ove4JHm1I7X0C98PZuLCyCsoUhAm2HpyGS+zr34qLM6iV06unnDvssvvEh5BkOfaLRF+N7cGoQ==",
-      "dev": true
-    },
     "eslint-plugin-import": {
       "version": "2.22.0",
       "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz",
@@ -6573,12 +6237,6 @@
         "strip-eof": "^1.0.0"
       }
     },
-    "exif2css": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/exif2css/-/exif2css-1.3.0.tgz",
-      "integrity": "sha512-RBjZaFcNumDz3bZiZGsR3Kg2jouyLuRnUPox7yE24WcvK8IhVdShAwSQHEqupGC0/DYDZsiunk+sv8CLmu4Lqg==",
-      "dev": true
-    },
     "exit": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@@ -6897,18 +6555,6 @@
       "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==",
       "dev": true
     },
-    "fast-safe-stringify": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
-      "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==",
-      "dev": true
-    },
-    "fastclick": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/fastclick/-/fastclick-1.0.6.tgz",
-      "integrity": "sha1-FhYlsnsaWAZAWTa9qaLBkm0Gvmo=",
-      "dev": true
-    },
     "fastq": {
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
@@ -6927,38 +6573,6 @@
         "bser": "2.1.1"
       }
     },
-    "fbemitter": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-2.1.1.tgz",
-      "integrity": "sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=",
-      "dev": true,
-      "requires": {
-        "fbjs": "^0.8.4"
-      }
-    },
-    "fbjs": {
-      "version": "0.8.17",
-      "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
-      "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
-      "dev": true,
-      "requires": {
-        "core-js": "^1.0.0",
-        "isomorphic-fetch": "^2.1.1",
-        "loose-envify": "^1.0.0",
-        "object-assign": "^4.1.0",
-        "promise": "^7.1.1",
-        "setimmediate": "^1.0.5",
-        "ua-parser-js": "^0.7.18"
-      },
-      "dependencies": {
-        "core-js": {
-          "version": "1.2.7",
-          "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
-          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
-          "dev": true
-        }
-      }
-    },
     "figgy-pudding": {
       "version": "3.5.2",
       "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@@ -7020,6 +6634,13 @@
         }
       }
     },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "dev": true,
+      "optional": true
+    },
     "fill-range": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@@ -7098,12 +6719,6 @@
       "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
       "dev": true
     },
-    "flexsearch": {
-      "version": "0.6.23",
-      "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.6.23.tgz",
-      "integrity": "sha512-WZWmAymAMpEX1un8gayWu3dgRk0Tmtm7QnZ1stPbzfihXyGQLneE3aEGxKIdZjHyh2WWUSPkBLjU/kkUy8rBXw==",
-      "dev": true
-    },
     "flush-write-stream": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@@ -7114,34 +6729,12 @@
         "readable-stream": "^2.3.6"
       }
     },
-    "flux": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/flux/-/flux-3.1.3.tgz",
-      "integrity": "sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=",
-      "dev": true,
-      "requires": {
-        "fbemitter": "^2.0.0",
-        "fbjs": "^0.8.0"
-      }
-    },
-    "font-awesome": {
-      "version": "4.7.0",
-      "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
-      "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=",
-      "dev": true
-    },
     "for-in": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
       "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
       "dev": true
     },
-    "formidable": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
-      "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==",
-      "dev": true
-    },
     "fragment-cache": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -7151,16 +6744,6 @@
         "map-cache": "^0.2.2"
       }
     },
-    "framesync": {
-      "version": "4.0.4",
-      "resolved": "https://registry.npmjs.org/framesync/-/framesync-4.0.4.tgz",
-      "integrity": "sha512-mdP0WvVHe0/qA62KG2LFUAOiWLng5GLpscRlwzBxu2VXOp6B8hNs5C5XlFigsMgrfDrr2YbqTsgdWZTc4RXRMQ==",
-      "dev": true,
-      "requires": {
-        "hey-listen": "^1.0.8",
-        "tslib": "^1.10.0"
-      }
-    },
     "from2": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
@@ -7317,25 +6900,6 @@
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
       "dev": true
     },
-    "get-intrinsic": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
-      "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
-      "dev": true,
-      "requires": {
-        "function-bind": "^1.1.1",
-        "has": "^1.0.3",
-        "has-symbols": "^1.0.3"
-      },
-      "dependencies": {
-        "has-symbols": {
-          "version": "1.0.3",
-          "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-          "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-          "dev": true
-        }
-      }
-    },
     "get-package-type": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
@@ -7399,16 +6963,6 @@
         "is-glob": "^4.0.1"
       }
     },
-    "global": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
-      "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
-      "dev": true,
-      "requires": {
-        "min-document": "^2.19.0",
-        "process": "^0.11.10"
-      }
-    },
     "global-modules": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
@@ -7526,12 +7080,6 @@
       "dev": true,
       "optional": true
     },
-    "gud": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
-      "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==",
-      "dev": true
-    },
     "hard-rejection": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
@@ -7647,32 +7195,6 @@
       "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz",
       "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng=="
     },
-    "hey-listen": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
-      "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==",
-      "dev": true
-    },
-    "highlight.js": {
-      "version": "9.15.8",
-      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.8.tgz",
-      "integrity": "sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA==",
-      "dev": true
-    },
-    "history": {
-      "version": "4.10.1",
-      "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
-      "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.1.2",
-        "loose-envify": "^1.2.0",
-        "resolve-pathname": "^3.0.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0",
-        "value-equal": "^1.0.1"
-      }
-    },
     "hmac-drbg": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -7856,12 +7378,6 @@
       "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
       "dev": true
     },
-    "immediate": {
-      "version": "3.0.6",
-      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
-      "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
-      "dev": true
-    },
     "import-fresh": {
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
@@ -7942,12 +7458,6 @@
       "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
       "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
     },
-    "inobounce": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/inobounce/-/inobounce-0.2.0.tgz",
-      "integrity": "sha512-eJt1MQEqLKanAFEE5DkSF36d9uyg8kLb/R6ysEF7udJOM5JZXidkhhGv6WoWbEcVdyRqZfYMjv6XbSIz7tD4CQ==",
-      "dev": true
-    },
     "internal-slot": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz",
@@ -7965,18 +7475,6 @@
       "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
       "dev": true
     },
-    "intl": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz",
-      "integrity": "sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94=",
-      "dev": true
-    },
-    "intl-format-cache": {
-      "version": "2.2.9",
-      "resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-2.2.9.tgz",
-      "integrity": "sha512-Zv/u8wRpekckv0cLkwpVdABYST4hZNTDaX7reFetrYTJwxExR2VyTqQm+l0WmL0Qo8Mjb9Tf33qnfj0T7pjxdQ==",
-      "dev": true
-    },
     "intl-messageformat": {
       "version": "9.2.0",
       "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.2.0.tgz",
@@ -7996,32 +7494,6 @@
         "@formatjs/intl-numberformat": "^5.3.3"
       }
     },
-    "intl-relativeformat": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/intl-relativeformat/-/intl-relativeformat-2.2.0.tgz",
-      "integrity": "sha512-4bV/7kSKaPEmu6ArxXf9xjv1ny74Zkwuey8Pm01NH4zggPP7JHwg2STk8Y3JdspCKRDriwIyLRfEXnj2ZLr4Bw==",
-      "dev": true,
-      "requires": {
-        "intl-messageformat": "^2.0.0"
-      },
-      "dependencies": {
-        "intl-messageformat": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz",
-          "integrity": "sha1-NFvNRt5jC3aDMwwuUhd/9eq0hPw=",
-          "dev": true,
-          "requires": {
-            "intl-messageformat-parser": "1.4.0"
-          }
-        },
-        "intl-messageformat-parser": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz",
-          "integrity": "sha1-tD1FqXRoytvkQzHXS7Ho3qRPwHU=",
-          "dev": true
-        }
-      }
-    },
     "invariant": {
       "version": "2.2.4",
       "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -8056,12 +7528,6 @@
         }
       }
     },
-    "is-arguments": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
-      "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
-      "dev": true
-    },
     "is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -8459,12 +7925,6 @@
         "istanbul-lib-report": "^3.0.0"
       }
     },
-    "jasny-bootstrap": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/jasny-bootstrap/-/jasny-bootstrap-3.1.3.tgz",
-      "integrity": "sha1-sHKmgdUMZJdiyVQ8emCT2bqi+Hs=",
-      "dev": true
-    },
     "jest": {
       "version": "26.4.2",
       "resolved": "https://registry.npmjs.org/jest/-/jest-26.4.2.tgz",
@@ -10951,12 +10411,6 @@
         }
       }
     },
-    "jquery": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
-      "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==",
-      "dev": true
-    },
     "js-base64": {
       "version": "2.6.4",
       "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
@@ -11205,35 +10659,6 @@
         "object.assign": "^4.1.0"
       }
     },
-    "katex": {
-      "version": "0.10.2",
-      "resolved": "https://registry.npmjs.org/katex/-/katex-0.10.2.tgz",
-      "integrity": "sha512-cQOmyIRoMloCoSIOZ1+gEwsksdJZ1EW4SWm3QzxSza/QsnZr6D4U1V9S4q+B/OLm2OQ8TCBecQ8MaIfnScI7cw==",
-      "dev": true,
-      "requires": {
-        "commander": "^2.19.0"
-      },
-      "dependencies": {
-        "commander": {
-          "version": "2.20.3",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-          "dev": true
-        }
-      }
-    },
-    "key-mirror": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/key-mirror/-/key-mirror-1.0.1.tgz",
-      "integrity": "sha1-ChMtXIqCo6T8199zL/lRDQSrNms=",
-      "dev": true
-    },
-    "keycode": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
-      "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=",
-      "dev": true
-    },
     "kind-of": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -11277,15 +10702,6 @@
         "type-check": "~0.4.0"
       }
     },
-    "lie": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
-      "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
-      "dev": true,
-      "requires": {
-        "immediate": "~3.0.5"
-      }
-    },
     "lines-and-columns": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
@@ -11353,33 +10769,6 @@
         }
       }
     },
-    "localforage": {
-      "version": "1.7.3",
-      "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz",
-      "integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==",
-      "dev": true,
-      "requires": {
-        "lie": "3.1.1"
-      }
-    },
-    "localforage-observable": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/localforage-observable/-/localforage-observable-2.0.0.tgz",
-      "integrity": "sha512-QZco9bV2XIJ7WU5Z+zJ7NT0xxN9J8BgHbJhdsA2k6OJGfR4jCBRoA/QiwQmKJhqX6TAFAjLN4sO3QEzNldVw0A==",
-      "dev": true,
-      "requires": {
-        "localforage": "^1.5.0",
-        "zen-observable": "^0.2.1"
-      },
-      "dependencies": {
-        "zen-observable": {
-          "version": "0.2.1",
-          "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.2.1.tgz",
-          "integrity": "sha1-xHZ2pkEyuEdaYapJ5RR1W1uWY/M=",
-          "dev": true
-        }
-      }
-    },
     "locate-path": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -11399,12 +10788,6 @@
       "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
       "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
     },
-    "lodash.camelcase": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
-      "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
-      "dev": true
-    },
     "lodash.escape": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz",
@@ -11690,23 +11073,6 @@
         "object-visit": "^1.0.0"
       }
     },
-    "mark.js": {
-      "version": "8.11.1",
-      "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
-      "integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U=",
-      "dev": true
-    },
-    "marked": {
-      "version": "github:mattermost/marked#3a9d0a5bd6d735df1a716ab50f73d58a1d865bdb",
-      "from": "github:mattermost/marked#3a9d0a5bd6d735df1a716ab50f73d58a1d865bdb",
-      "dev": true
-    },
-    "material-colors": {
-      "version": "1.2.6",
-      "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
-      "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==",
-      "dev": true
-    },
     "mattermost-redux": {
       "version": "5.27.0",
       "resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.27.0.tgz",
@@ -11747,492 +11113,6 @@
         }
       }
     },
-    "mattermost-webapp": {
-      "version": "github:mattermost/mattermost-webapp#23f5f93d9f12a7e2b5623e5cee6814366abd9a0f",
-      "from": "github:mattermost/mattermost-webapp#23f5f93d9f12a7e2b5623e5cee6814366abd9a0f",
-      "dev": true,
-      "requires": {
-        "bootstrap": "3.4.1",
-        "bootstrap-colorpicker": "2.5.2",
-        "chart.js": "2.8.0",
-        "compass-mixins": "0.12.10",
-        "core-js": "3.1.4",
-        "css-vars-ponyfill": "2.0.2",
-        "emoji-regex": "8.0.0",
-        "exif2css": "1.3.0",
-        "fastclick": "1.0.6",
-        "flexsearch": "0.6.23",
-        "flux": "3.1.3",
-        "font-awesome": "4.7.0",
-        "highlight.js": "9.15.8",
-        "hoist-non-react-statics": "3.3.0",
-        "html-to-react": "1.3.4",
-        "inobounce": "0.2.0",
-        "intl": "1.2.5",
-        "jasny-bootstrap": "3.1.3",
-        "jquery": "3.4.1",
-        "katex": "0.10.2",
-        "key-mirror": "1.0.1",
-        "localforage": "1.7.3",
-        "localforage-observable": "2.0.0",
-        "mark.js": "8.11.1",
-        "marked": "github:mattermost/marked#3a9d0a5bd6d735df1a716ab50f73d58a1d865bdb",
-        "mattermost-redux": "github:mattermost/mattermost-redux#40e5bd5a18daa919c62a06b360aa27f2e6ed3e13",
-        "moment-timezone": "0.5.26",
-        "pdfjs-dist": "2.0.489",
-        "perfect-scrollbar": "0.8.1",
-        "popmotion": "8.7.0",
-        "popper.js": "1.15.0",
-        "prop-types": "15.7.2",
-        "react": "16.8.6",
-        "react-addons-pure-render-mixin": "15.6.2",
-        "react-bootstrap": "github:mattermost/react-bootstrap#c6957962364e0818a51bbfd13e89919903b422d6",
-        "react-color": "2.17.3",
-        "react-contextmenu": "2.11.0",
-        "react-custom-scrollbars": "4.2.1",
-        "react-day-picker": "7.3.0",
-        "react-dom": "16.8.6",
-        "react-hot-loader": "4.12.8",
-        "react-intl": "2.9.0",
-        "react-overlays": "1.2.0",
-        "react-redux": "5.1.1",
-        "react-router-dom": "5.0.1",
-        "react-select": "2.4.4",
-        "react-transition-group": "4.2.1",
-        "react-virtualized-auto-sizer": "1.0.2",
-        "react-window": "github:mattermost/react-window#4c694b7633a2527a42c4952235719fefa3accb80",
-        "rebound": "0.1.0",
-        "redux": "4.0.4",
-        "redux-batched-actions": "0.4.1",
-        "redux-persist": "4.10.2",
-        "regenerator-runtime": "^0.13.3",
-        "reselect": "4.0.0",
-        "superagent": "5.1.0",
-        "xregexp": "4.2.4",
-        "zen-observable": "0.8.14"
-      },
-      "dependencies": {
-        "core-js": {
-          "version": "3.1.4",
-          "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz",
-          "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==",
-          "dev": true
-        },
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        },
-        "domelementtype": {
-          "version": "1.3.1",
-          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
-          "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
-          "dev": true
-        },
-        "domhandler": {
-          "version": "2.4.2",
-          "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
-          "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
-          "dev": true,
-          "requires": {
-            "domelementtype": "1"
-          }
-        },
-        "domutils": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
-          "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
-          "dev": true,
-          "requires": {
-            "dom-serializer": "0",
-            "domelementtype": "1"
-          }
-        },
-        "emoji-regex": {
-          "version": "8.0.0",
-          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-          "dev": true
-        },
-        "entities": {
-          "version": "1.1.2",
-          "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
-          "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
-          "dev": true
-        },
-        "form-data": {
-          "version": "2.5.0",
-          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.0.tgz",
-          "integrity": "sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA==",
-          "dev": true,
-          "requires": {
-            "asynckit": "^0.4.0",
-            "combined-stream": "^1.0.6",
-            "mime-types": "^2.1.12"
-          }
-        },
-        "hoist-non-react-statics": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
-          "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
-          "dev": true,
-          "requires": {
-            "react-is": "^16.7.0"
-          }
-        },
-        "html-to-react": {
-          "version": "1.3.4",
-          "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.3.4.tgz",
-          "integrity": "sha512-/tWDdb/8Koi/QEP5YUY1653PcDpBnnMblXRhotnTuhFDjI1Fc6Wzox5d4sw73Xk5rM2OdM5np4AYjT/US/Wj7Q==",
-          "dev": true,
-          "requires": {
-            "domhandler": "^2.4.2",
-            "escape-string-regexp": "^1.0.5",
-            "htmlparser2": "^3.10.0",
-            "lodash.camelcase": "^4.3.0",
-            "ramda": "^0.26"
-          }
-        },
-        "htmlparser2": {
-          "version": "3.10.1",
-          "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
-          "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
-          "dev": true,
-          "requires": {
-            "domelementtype": "^1.3.1",
-            "domhandler": "^2.3.0",
-            "domutils": "^1.5.1",
-            "entities": "^1.1.1",
-            "inherits": "^2.0.1",
-            "readable-stream": "^3.1.1"
-          }
-        },
-        "intl-messageformat": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-2.2.0.tgz",
-          "integrity": "sha1-NFvNRt5jC3aDMwwuUhd/9eq0hPw=",
-          "dev": true,
-          "requires": {
-            "intl-messageformat-parser": "1.4.0"
-          }
-        },
-        "intl-messageformat-parser": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz",
-          "integrity": "sha1-tD1FqXRoytvkQzHXS7Ho3qRPwHU=",
-          "dev": true
-        },
-        "mattermost-redux": {
-          "version": "github:mattermost/mattermost-redux#40e5bd5a18daa919c62a06b360aa27f2e6ed3e13",
-          "from": "github:mattermost/mattermost-redux#40e5bd5a18daa919c62a06b360aa27f2e6ed3e13",
-          "dev": true,
-          "requires": {
-            "deep-equal": "1.0.1",
-            "eslint-plugin-header": "3.0.0",
-            "form-data": "2.5.0",
-            "gfycat-sdk": "1.4.18",
-            "harmony-reflect": "1.6.1",
-            "isomorphic-fetch": "2.2.1",
-            "mime-db": "1.40.0",
-            "moment-timezone": "0.5.26",
-            "redux": "4.0.4",
-            "redux-action-buffer": "1.2.0",
-            "redux-batched-actions": "0.4.1",
-            "redux-offline": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df",
-            "redux-persist": "4.9.1",
-            "redux-thunk": "2.3.0",
-            "reselect": "4.0.0",
-            "serialize-error": "2.1.0",
-            "shallow-equals": "1.0.0"
-          },
-          "dependencies": {
-            "redux-persist": {
-              "version": "4.9.1",
-              "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-4.9.1.tgz",
-              "integrity": "sha512-XoOmfPyo9GEU/WLH9FgB47dNIN9l5ArjHes4o7vUWx9nxZoPxnVodhuHdyc4Ot+fMkdj3L2LTqSHhwrkr0QFUg==",
-              "dev": true,
-              "requires": {
-                "json-stringify-safe": "^5.0.1",
-                "lodash": "^4.17.4",
-                "lodash-es": "^4.17.4"
-              }
-            }
-          }
-        },
-        "mime": {
-          "version": "2.4.6",
-          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
-          "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==",
-          "dev": true
-        },
-        "moment-timezone": {
-          "version": "0.5.26",
-          "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.26.tgz",
-          "integrity": "sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g==",
-          "dev": true,
-          "requires": {
-            "moment": ">= 2.9.0"
-          }
-        },
-        "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-          "dev": true
-        },
-        "react": {
-          "version": "16.8.6",
-          "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz",
-          "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==",
-          "dev": true,
-          "requires": {
-            "loose-envify": "^1.1.0",
-            "object-assign": "^4.1.1",
-            "prop-types": "^15.6.2",
-            "scheduler": "^0.13.6"
-          }
-        },
-        "react-bootstrap": {
-          "version": "github:mattermost/react-bootstrap#c6957962364e0818a51bbfd13e89919903b422d6",
-          "from": "github:mattermost/react-bootstrap#c6957962364e0818a51bbfd13e89919903b422d6",
-          "dev": true,
-          "requires": {
-            "@babel/runtime-corejs2": "^7.0.0",
-            "classnames": "^2.2.5",
-            "dom-helpers": "^3.2.0",
-            "invariant": "^2.2.4",
-            "keycode": "^2.2.0",
-            "prop-types": "^15.6.1",
-            "prop-types-extra": "^1.0.1",
-            "react-overlays": "^0.8.0",
-            "react-prop-types": "^0.4.0",
-            "react-transition-group": "^2.0.0",
-            "uncontrollable": "^5.0.0",
-            "warning": "^3.0.0"
-          },
-          "dependencies": {
-            "react-overlays": {
-              "version": "0.8.3",
-              "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz",
-              "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==",
-              "dev": true,
-              "requires": {
-                "classnames": "^2.2.5",
-                "dom-helpers": "^3.2.1",
-                "prop-types": "^15.5.10",
-                "prop-types-extra": "^1.0.1",
-                "react-transition-group": "^2.2.0",
-                "warning": "^3.0.0"
-              }
-            },
-            "react-transition-group": {
-              "version": "2.9.0",
-              "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
-              "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
-              "dev": true,
-              "requires": {
-                "dom-helpers": "^3.4.0",
-                "loose-envify": "^1.4.0",
-                "prop-types": "^15.6.2",
-                "react-lifecycles-compat": "^3.0.4"
-              }
-            }
-          }
-        },
-        "react-intl": {
-          "version": "2.9.0",
-          "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.9.0.tgz",
-          "integrity": "sha512-27jnDlb/d2A7mSJwrbOBnUgD+rPep+abmoJE511Tf8BnoONIAUehy/U1zZCHGO17mnOwMWxqN4qC0nW11cD6rA==",
-          "dev": true,
-          "requires": {
-            "hoist-non-react-statics": "^3.3.0",
-            "intl-format-cache": "^2.0.5",
-            "intl-messageformat": "^2.1.0",
-            "intl-relativeformat": "^2.1.0",
-            "invariant": "^2.1.1"
-          }
-        },
-        "react-overlays": {
-          "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-1.2.0.tgz",
-          "integrity": "sha512-i/FCV8wR6aRaI+Kz/dpJhOdyx+ah2tN1RhT9InPrexyC4uzf3N4bNayFTGtUeQVacj57j1Mqh1CwV60/5153Iw==",
-          "dev": true,
-          "requires": {
-            "classnames": "^2.2.6",
-            "dom-helpers": "^3.4.0",
-            "prop-types": "^15.6.2",
-            "prop-types-extra": "^1.1.0",
-            "react-context-toolbox": "^2.0.2",
-            "react-popper": "^1.3.2",
-            "uncontrollable": "^6.0.0",
-            "warning": "^4.0.2"
-          },
-          "dependencies": {
-            "uncontrollable": {
-              "version": "6.2.3",
-              "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-6.2.3.tgz",
-              "integrity": "sha512-VgOAoBU2ptCL2bfTG2Mra0I8i1u6Aq84AFonD5tmCAYSfs3hWvr2Rlw0q2ntoxXTHjcQOmZOh3FKaN+UZVyREQ==",
-              "dev": true,
-              "requires": {
-                "@babel/runtime": "^7.4.5",
-                "invariant": "^2.2.4"
-              }
-            },
-            "warning": {
-              "version": "4.0.3",
-              "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
-              "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
-              "dev": true,
-              "requires": {
-                "loose-envify": "^1.0.0"
-              }
-            }
-          }
-        },
-        "react-redux": {
-          "version": "5.1.1",
-          "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz",
-          "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==",
-          "dev": true,
-          "requires": {
-            "@babel/runtime": "^7.1.2",
-            "hoist-non-react-statics": "^3.1.0",
-            "invariant": "^2.2.4",
-            "loose-envify": "^1.1.0",
-            "prop-types": "^15.6.1",
-            "react-is": "^16.6.0",
-            "react-lifecycles-compat": "^3.0.0"
-          }
-        },
-        "react-select": {
-          "version": "2.4.4",
-          "resolved": "https://registry.npmjs.org/react-select/-/react-select-2.4.4.tgz",
-          "integrity": "sha512-C4QPLgy9h42J/KkdrpVxNmkY6p4lb49fsrbDk/hRcZpX7JvZPNb6mGj+c5SzyEtBv1DmQ9oPH4NmhAFvCrg8Jw==",
-          "dev": true,
-          "requires": {
-            "classnames": "^2.2.5",
-            "emotion": "^9.1.2",
-            "memoize-one": "^5.0.0",
-            "prop-types": "^15.6.0",
-            "raf": "^3.4.0",
-            "react-input-autosize": "^2.2.1",
-            "react-transition-group": "^2.2.1"
-          },
-          "dependencies": {
-            "react-transition-group": {
-              "version": "2.9.0",
-              "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
-              "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
-              "dev": true,
-              "requires": {
-                "dom-helpers": "^3.4.0",
-                "loose-envify": "^1.4.0",
-                "prop-types": "^15.6.2",
-                "react-lifecycles-compat": "^3.0.4"
-              }
-            }
-          }
-        },
-        "react-transition-group": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.2.1.tgz",
-          "integrity": "sha512-IXrPr93VzCPupwm2O6n6C2kJIofJ/Rp5Ltihhm9UfE8lkuVX2ng/SUUl/oWjblybK9Fq2Io7LGa6maVqPB762Q==",
-          "dev": true,
-          "requires": {
-            "@babel/runtime": "^7.4.5",
-            "dom-helpers": "^3.4.0",
-            "loose-envify": "^1.4.0",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "readable-stream": {
-          "version": "3.6.0",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-          "dev": true,
-          "requires": {
-            "inherits": "^2.0.3",
-            "string_decoder": "^1.1.1",
-            "util-deprecate": "^1.0.1"
-          }
-        },
-        "redux": {
-          "version": "4.0.4",
-          "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz",
-          "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==",
-          "dev": true,
-          "requires": {
-            "loose-envify": "^1.4.0",
-            "symbol-observable": "^1.2.0"
-          }
-        },
-        "redux-persist": {
-          "version": "4.10.2",
-          "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-4.10.2.tgz",
-          "integrity": "sha512-U+e0ieMGC69Zr72929iJW40dEld7Mflh6mu0eJtVMLGfMq/aJqjxUM1hzyUWMR1VUyAEEdPHuQmeq5ti9krIgg==",
-          "dev": true,
-          "requires": {
-            "json-stringify-safe": "^5.0.1",
-            "lodash": "^4.17.4",
-            "lodash-es": "^4.17.4"
-          }
-        },
-        "scheduler": {
-          "version": "0.13.6",
-          "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
-          "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
-          "dev": true,
-          "requires": {
-            "loose-envify": "^1.1.0",
-            "object-assign": "^4.1.1"
-          }
-        },
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        },
-        "serialize-error": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
-          "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=",
-          "dev": true
-        },
-        "superagent": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.1.0.tgz",
-          "integrity": "sha512-7V6JVx5N+eTL1MMqRBX0v0bG04UjrjAvvZJTF/VDH/SH2GjSLqlrcYepFlpTrXpm37aSY6h3GGVWGxXl/98TKA==",
-          "dev": true,
-          "requires": {
-            "component-emitter": "^1.3.0",
-            "cookiejar": "^2.1.2",
-            "debug": "^4.1.1",
-            "fast-safe-stringify": "^2.0.6",
-            "form-data": "^2.3.3",
-            "formidable": "^1.2.1",
-            "methods": "^1.1.2",
-            "mime": "^2.4.4",
-            "qs": "^6.7.0",
-            "readable-stream": "^3.4.0",
-            "semver": "^6.1.1"
-          }
-        },
-        "uncontrollable": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz",
-          "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==",
-          "dev": true,
-          "requires": {
-            "invariant": "^2.2.4"
-          }
-        }
-      }
-    },
     "md5.js": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -12461,12 +11341,6 @@
       "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
       "dev": true
     },
-    "methods": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
-      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
-      "dev": true
-    },
     "micromark": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz",
@@ -12733,12 +11607,6 @@
         }
       }
     },
-    "mime-db": {
-      "version": "1.40.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
-      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
-      "dev": true
-    },
     "mime-types": {
       "version": "2.1.24",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
@@ -12760,32 +11628,12 @@
       "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
       "dev": true
     },
-    "min-document": {
-      "version": "2.19.0",
-      "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
-      "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
-      "dev": true,
-      "requires": {
-        "dom-walk": "^0.1.0"
-      }
-    },
     "min-indent": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
       "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
       "dev": true
     },
-    "mini-create-react-context": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz",
-      "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.4.0",
-        "gud": "^1.0.0",
-        "tiny-warning": "^1.0.2"
-      }
-    },
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -13021,6 +11869,13 @@
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
       "dev": true
     },
+    "nan": {
+      "version": "2.17.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
+      "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==",
+      "dev": true,
+      "optional": true
+    },
     "nanoid": {
       "version": "2.1.11",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
@@ -13090,12 +11945,6 @@
       "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
       "dev": true
     },
-    "node-ensure": {
-      "version": "0.0.0",
-      "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
-      "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=",
-      "dev": true
-    },
     "node-fetch": {
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -13522,15 +12371,6 @@
         }
       }
     },
-    "nopt": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
-      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
-      "dev": true,
-      "requires": {
-        "abbrev": "1"
-      }
-    },
     "normalize-package-data": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -13947,23 +12787,6 @@
       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
     },
-    "path-to-regexp": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
-      "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
-      "dev": true,
-      "requires": {
-        "isarray": "0.0.1"
-      },
-      "dependencies": {
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
-          "dev": true
-        }
-      }
-    },
     "path-type": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -13982,22 +12805,6 @@
         "sha.js": "^2.4.8"
       }
     },
-    "pdfjs-dist": {
-      "version": "2.0.489",
-      "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.0.489.tgz",
-      "integrity": "sha1-Y+VLKSqGeQpFRpfrRNQ0e4+/rSc=",
-      "dev": true,
-      "requires": {
-        "node-ensure": "^0.0.0",
-        "worker-loader": "^1.1.1"
-      }
-    },
-    "perfect-scrollbar": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-0.8.1.tgz",
-      "integrity": "sha512-RNC5tX/JMRYR+qVdJTEAWnRxw0Yf9lvbO8lTuAOvgDODkiA8lveTSkvrNMhmaGKEyimJpJl+myb/syVS9YyPuw==",
-      "dev": true
-    },
     "performance-now": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -14069,27 +12876,6 @@
         }
       }
     },
-    "popmotion": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-8.7.0.tgz",
-      "integrity": "sha512-RnJMbWN+TEBT0tgG+3WS0O1i9LAq+senXSAcKw+srGviT1c9G+iKUXrp24D2w6Og0mDd61rEfKz3pFrFCdKn5Q==",
-      "dev": true,
-      "requires": {
-        "@popmotion/easing": "^1.0.1",
-        "@popmotion/popcorn": "^0.4.0",
-        "framesync": "^4.0.0",
-        "hey-listen": "^1.0.5",
-        "style-value-types": "^3.1.4",
-        "stylefire": "^4.1.3",
-        "tslib": "^1.9.1"
-      }
-    },
-    "popper.js": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz",
-      "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==",
-      "dev": true
-    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -14250,15 +13036,6 @@
       "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
       "dev": true
     },
-    "promise": {
-      "version": "7.3.1",
-      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
-      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
-      "dev": true,
-      "requires": {
-        "asap": "~2.0.3"
-      }
-    },
     "promise-inflight": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@@ -14403,34 +13180,6 @@
       "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
       "dev": true
     },
-    "qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-      "dev": true,
-      "requires": {
-        "side-channel": "^1.0.4"
-      },
-      "dependencies": {
-        "object-inspect": {
-          "version": "1.12.2",
-          "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
-          "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
-          "dev": true
-        },
-        "side-channel": {
-          "version": "1.0.4",
-          "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-          "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
-          "dev": true,
-          "requires": {
-            "call-bind": "^1.0.0",
-            "get-intrinsic": "^1.0.2",
-            "object-inspect": "^1.9.0"
-          }
-        }
-      }
-    },
     "querystring": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@@ -14468,12 +13217,6 @@
       "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=",
       "dev": true
     },
-    "ramda": {
-      "version": "0.26.1",
-      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
-      "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==",
-      "dev": true
-    },
     "randexp": {
       "version": "0.4.6",
       "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
@@ -14513,16 +13256,6 @@
         "prop-types": "^15.6.2"
       }
     },
-    "react-addons-pure-render-mixin": {
-      "version": "15.6.2",
-      "resolved": "https://registry.npmjs.org/react-addons-pure-render-mixin/-/react-addons-pure-render-mixin-15.6.2.tgz",
-      "integrity": "sha1-a4P0C2s27kBzXL1hJes/E84c3ck=",
-      "dev": true,
-      "requires": {
-        "fbjs": "^0.8.4",
-        "object-assign": "^4.1.0"
-      }
-    },
     "react-bootstrap": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.3.0.tgz",
@@ -14587,36 +13320,6 @@
         }
       }
     },
-    "react-color": {
-      "version": "2.17.3",
-      "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz",
-      "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==",
-      "dev": true,
-      "requires": {
-        "@icons/material": "^0.2.4",
-        "lodash": "^4.17.11",
-        "material-colors": "^1.2.1",
-        "prop-types": "^15.5.10",
-        "reactcss": "^1.2.0",
-        "tinycolor2": "^1.4.1"
-      }
-    },
-    "react-context-toolbox": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/react-context-toolbox/-/react-context-toolbox-2.0.2.tgz",
-      "integrity": "sha512-tY4j0imkYC3n5ZlYSgFkaw7fmlCp3IoQQ6DxpqeNHzcD0hf+6V+/HeJxviLUZ1Rv1Yn3N3xyO2EhkkZwHn0m1A==",
-      "dev": true
-    },
-    "react-contextmenu": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/react-contextmenu/-/react-contextmenu-2.11.0.tgz",
-      "integrity": "sha512-vT9QV9p/9h1BSIvmajRVG3KsgjuBnISpEQp0F1QYsUPFMe3VOKV2l7IiD8yrNUyXYZKrWMqI0YKsaBwGSRVgJg==",
-      "dev": true,
-      "requires": {
-        "classnames": "^2.2.5",
-        "object-assign": "^4.1.0"
-      }
-    },
     "react-custom-scrollbars": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz",
@@ -14627,63 +13330,6 @@
         "raf": "^3.1.0"
       }
     },
-    "react-day-picker": {
-      "version": "7.3.0",
-      "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-7.3.0.tgz",
-      "integrity": "sha512-t2kz0Zy4P5U4qwU5YhsBq2QGmypP8L/u+89TSnuD0h4dYKSEDQArFPWfin9gv8erV1ciR1Wzr485TMaYnI7FTw==",
-      "dev": true,
-      "requires": {
-        "prop-types": "^15.6.2"
-      }
-    },
-    "react-dom": {
-      "version": "16.8.6",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
-      "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==",
-      "dev": true,
-      "requires": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1",
-        "prop-types": "^15.6.2",
-        "scheduler": "^0.13.6"
-      },
-      "dependencies": {
-        "scheduler": {
-          "version": "0.13.6",
-          "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
-          "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
-          "dev": true,
-          "requires": {
-            "loose-envify": "^1.1.0",
-            "object-assign": "^4.1.1"
-          }
-        }
-      }
-    },
-    "react-hot-loader": {
-      "version": "4.12.8",
-      "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.8.tgz",
-      "integrity": "sha512-/Df2J3znMHzRzI6CW0dTOIWD2sjkVHxv56XCqujAo9mR+k2PVTiGjUgYBiGPGsix9zQzgCRfOKca93o9Zdj2vQ==",
-      "dev": true,
-      "requires": {
-        "fast-levenshtein": "^2.0.6",
-        "global": "^4.3.0",
-        "hoist-non-react-statics": "^3.3.0",
-        "loader-utils": "^1.1.0",
-        "prop-types": "^15.6.1",
-        "react-lifecycles-compat": "^3.0.4",
-        "shallowequal": "^1.0.2",
-        "source-map": "^0.7.3"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.7.3",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
-          "dev": true
-        }
-      }
-    },
     "react-input-autosize": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz",
@@ -14751,55 +13397,6 @@
         }
       }
     },
-    "react-popper": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
-      "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.1.2",
-        "create-react-context": "^0.3.0",
-        "deep-equal": "^1.1.1",
-        "popper.js": "^1.14.4",
-        "prop-types": "^15.6.1",
-        "typed-styles": "^0.0.7",
-        "warning": "^4.0.2"
-      },
-      "dependencies": {
-        "deep-equal": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
-          "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
-          "dev": true,
-          "requires": {
-            "is-arguments": "^1.0.4",
-            "is-date-object": "^1.0.1",
-            "is-regex": "^1.0.4",
-            "object-is": "^1.0.1",
-            "object-keys": "^1.1.1",
-            "regexp.prototype.flags": "^1.2.0"
-          }
-        },
-        "warning": {
-          "version": "4.0.3",
-          "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
-          "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
-          "dev": true,
-          "requires": {
-            "loose-envify": "^1.0.0"
-          }
-        }
-      }
-    },
-    "react-prop-types": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz",
-      "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=",
-      "dev": true,
-      "requires": {
-        "warning": "^3.0.0"
-      }
-    },
     "react-redux": {
       "version": "7.2.1",
       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz",
@@ -14812,39 +13409,6 @@
         "react-is": "^16.9.0"
       }
     },
-    "react-router": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz",
-      "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.1.2",
-        "history": "^4.9.0",
-        "hoist-non-react-statics": "^3.1.0",
-        "loose-envify": "^1.3.1",
-        "mini-create-react-context": "^0.3.0",
-        "path-to-regexp": "^1.7.0",
-        "prop-types": "^15.6.2",
-        "react-is": "^16.6.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
-      }
-    },
-    "react-router-dom": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz",
-      "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.1.2",
-        "history": "^4.9.0",
-        "loose-envify": "^1.3.1",
-        "prop-types": "^15.6.2",
-        "react-router": "5.0.1",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
-      }
-    },
     "react-select": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.1.0.tgz",
@@ -14899,38 +13463,6 @@
         }
       }
     },
-    "react-virtualized-auto-sizer": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz",
-      "integrity": "sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==",
-      "dev": true
-    },
-    "react-window": {
-      "version": "github:mattermost/react-window#4c694b7633a2527a42c4952235719fefa3accb80",
-      "from": "github:mattermost/react-window#4c694b7633a2527a42c4952235719fefa3accb80",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.0.0",
-        "memoize-one": "^3.1.1"
-      },
-      "dependencies": {
-        "memoize-one": {
-          "version": "3.1.1",
-          "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-3.1.1.tgz",
-          "integrity": "sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA==",
-          "dev": true
-        }
-      }
-    },
-    "reactcss": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
-      "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
-      "dev": true,
-      "requires": {
-        "lodash": "^4.0.1"
-      }
-    },
     "read-pkg": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
@@ -15056,12 +13588,6 @@
         "picomatch": "^2.2.1"
       }
     },
-    "rebound": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/rebound/-/rebound-0.1.0.tgz",
-      "integrity": "sha1-BjjGGpNma7UVpYoD4c+zQCHoi3I=",
-      "dev": true
-    },
     "redent": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -15086,12 +13612,6 @@
       "resolved": "https://registry.npmjs.org/redux-action-buffer/-/redux-action-buffer-1.2.0.tgz",
       "integrity": "sha1-LsCh2JnMn29EzN60Me5SrUHdl1U="
     },
-    "redux-batched-actions": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/redux-batched-actions/-/redux-batched-actions-0.4.1.tgz",
-      "integrity": "sha512-r6tLDyBP3U9cXNLEHs0n1mX5TQfmk6xE0Y9uinYZ5HOyAWDgIJxYqRRkU/bC6XrJ4nS7tasNbxaHJHVmf9UdkA==",
-      "dev": true
-    },
     "redux-devtools-core": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/redux-devtools-core/-/redux-devtools-core-0.2.1.tgz",
@@ -15378,12 +13898,6 @@
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
     },
-    "resolve-pathname": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
-      "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==",
-      "dev": true
-    },
     "resolve-url": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -15869,12 +14383,6 @@
       "resolved": "https://registry.npmjs.org/shallow-equals/-/shallow-equals-1.0.0.tgz",
       "integrity": "sha1-JLdL8cY0wR7Uxxgqbfb7MA3OQ5A="
     },
-    "shallowequal": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
-      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
-      "dev": true
-    },
     "shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -16529,40 +15037,6 @@
         "inline-style-parser": "0.1.1"
       }
     },
-    "style-value-types": {
-      "version": "3.1.9",
-      "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-3.1.9.tgz",
-      "integrity": "sha512-050uqgB7WdvtgacoQKm+4EgKzJExVq0sieKBQQtJiU3Muh6MYcCp4T3M8+dfl6VOF2LR0NNwXBP1QYEed8DfIw==",
-      "dev": true,
-      "requires": {
-        "hey-listen": "^1.0.8",
-        "tslib": "^1.10.0"
-      }
-    },
-    "stylefire": {
-      "version": "4.1.4",
-      "resolved": "https://registry.npmjs.org/stylefire/-/stylefire-4.1.4.tgz",
-      "integrity": "sha512-bp9nNTTFHdIQp/4szBuF2z85rMAq5oySeAHdpNgPTcVlXDrwsi1FjjOLug/4+yx1p8eMFFGrkAex7b5/M95ivg==",
-      "dev": true,
-      "requires": {
-        "@popmotion/popcorn": "^0.4.0",
-        "framesync": "^4.0.0",
-        "hey-listen": "^1.0.8",
-        "style-value-types": "^3.1.4"
-      }
-    },
-    "stylis": {
-      "version": "3.5.4",
-      "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
-      "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==",
-      "dev": true
-    },
-    "stylis-rule-sheet": {
-      "version": "0.0.10",
-      "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
-      "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==",
-      "dev": true
-    },
     "supports-color": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -16789,24 +15263,6 @@
         "setimmediate": "^1.0.4"
       }
     },
-    "tiny-invariant": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
-      "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==",
-      "dev": true
-    },
-    "tiny-warning": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
-      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
-      "dev": true
-    },
-    "tinycolor2": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
-      "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=",
-      "dev": true
-    },
     "tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -16887,15 +15343,6 @@
         "to-no-case": "^1.0.0"
       }
     },
-    "touch": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/touch/-/touch-2.0.2.tgz",
-      "integrity": "sha512-qjNtvsFXTRq7IuMLweVgFxmEuQ6gLbRs2jQxL80TtZ31dEKWYIxRXquij6w6VimyDek5hD3PytljHmEtAs2u0A==",
-      "dev": true,
-      "requires": {
-        "nopt": "~1.0.10"
-      }
-    },
     "tr46": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz",
@@ -16992,12 +15439,6 @@
       "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
       "dev": true
     },
-    "typed-styles": {
-      "version": "0.0.7",
-      "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
-      "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==",
-      "dev": true
-    },
     "typedarray": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -17019,12 +15460,6 @@
       "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==",
       "dev": true
     },
-    "ua-parser-js": {
-      "version": "0.7.33",
-      "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
-      "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==",
-      "dev": true
-    },
     "uncontrollable": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz",
@@ -17354,12 +15789,6 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
-    "value-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
-      "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==",
-      "dev": true
-    },
     "vfile": {
       "version": "5.3.7",
       "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz",
@@ -17420,15 +15849,6 @@
         "makeerror": "1.0.x"
       }
     },
-    "warning": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
-      "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
-      "dev": true,
-      "requires": {
-        "loose-envify": "^1.0.0"
-      }
-    },
     "watchpack": {
       "version": "1.7.5",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
@@ -17484,7 +15904,11 @@
           "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
           "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
           "dev": true,
-          "optional": true
+          "optional": true,
+          "requires": {
+            "bindings": "^1.5.0",
+            "nan": "^2.12.1"
+          }
         },
         "glob-parent": {
           "version": "3.1.0",
@@ -17907,28 +16331,6 @@
         "errno": "~0.1.7"
       }
     },
-    "worker-loader": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-1.1.1.tgz",
-      "integrity": "sha512-qJZLVS/jMCBITDzPo/RuweYSIG8VJP5P67mP/71alGyTZRe1LYJFdwLjLalY3T5ifx0bMDRD3OB6P2p1escvlg==",
-      "dev": true,
-      "requires": {
-        "loader-utils": "^1.0.0",
-        "schema-utils": "^0.4.0"
-      },
-      "dependencies": {
-        "schema-utils": {
-          "version": "0.4.7",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
-          "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
-          "dev": true,
-          "requires": {
-            "ajv": "^6.1.0",
-            "ajv-keywords": "^3.1.0"
-          }
-        }
-      }
-    },
     "wrap-ansi": {
       "version": "6.2.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -18033,15 +16435,6 @@
       "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
       "dev": true
     },
-    "xregexp": {
-      "version": "4.2.4",
-      "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.4.tgz",
-      "integrity": "sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime-corejs2": "^7.2.0"
-      }
-    },
     "xtend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -18118,12 +16511,6 @@
         "camelcase": "^5.0.0",
         "decamelize": "^1.2.0"
       }
-    },
-    "zen-observable": {
-      "version": "0.8.14",
-      "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.14.tgz",
-      "integrity": "sha512-kQz39uonEjEESwh+qCi83kcC3rZJGh4mrZW7xjkSQYXkq//JZHTtKo+6yuVloTgMtzsIWOJrjIrKvk/dqm0L5g==",
-      "dev": true
     }
   }
 }
diff --git a/webapp/package.json b/webapp/package.json
index 6e92d11..453abb3 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -55,7 +55,6 @@
     "jest": "26.4.2",
     "jest-canvas-mock": "2.2.0",
     "jest-junit": "11.1.0",
-    "mattermost-webapp": "github:mattermost/mattermost-webapp#23f5f93d9f12a7e2b5623e5cee6814366abd9a0f",
     "node-sass": "8.0.0",
     "sass-loader": "10.4.1",
     "style-loader": "1.2.1",
@@ -116,9 +115,6 @@
     "setupFiles": [
       "jest-canvas-mock"
     ],
-    "setupFilesAfterEnv": [
-      "<rootDir>/tests/setup.js"
-    ],
     "testURL": "http://localhost:8065"
   }
 }


Test patch:
diff --git a/e2e/playwright/.gitignore b/e2e/playwright/.gitignore
new file mode 100644
index 0000000..9cb4e32
--- /dev/null
+++ b/e2e/playwright/.gitignore
@@ -0,0 +1,5 @@
+.env
+
+screenshots
+playwright-report
+test-results
diff --git a/e2e/playwright/README.md b/e2e/playwright/README.md
new file mode 100644
index 0000000..ef2848a
--- /dev/null
+++ b/e2e/playwright/README.md
@@ -0,0 +1,35 @@
+In order to get your environment set up to run [Playwright](https://playwright.dev) tests, you can run `./setup-environment`, or run equivalent commands for your current setup.
+
+What this script does:
+
+- Navigate to the folder above `mattermost-plugin-github`
+- Clone `mattermost-server` (if it is already cloned there, please have a clean git index to avoid issues with conflicts)
+- `cd mattermost-server`
+- Install webapp dependencies - `cd webapp && npm i`
+- Install Playwright test dependencies - `cd ../e2e-tests/playwright && npm i`
+- Install Playwright - `npx install playwright`
+- Install GitHub plugin e2e dependencies - `cd ../../../mattermost-plugin-github/e2e/playwright && npm i`
+- Build and deploy plugin with mock OAuth server URL env var - `PLUGIN_E2E_MOCK_OAUTH_SERVER_URL=http://localhost:8080 make deploy`
+
+-----
+
+Then to run the tests:
+
+Start Mattermost server:
+- `cd <path>/mattermost-server/server`
+- `make test-data`
+- `make run-server`
+
+Run test:
+- Create a personal access token from GitHub
+- Set `PLUGIN_E2E_MOCK_OAUTH_TOKEN` environment variable to access token
+- `cd <path>/mattermost-plugin-github/e2e/playwright`
+- `npm test`
+
+To see the test report:
+- `cd <path>/mattermost-plugin-github/e2e/playwright`
+- `npm run show-report`
+- Navigate to http://localhost:9323
+
+To see test screenshots:
+- `cd <path>/mattermost-plugin-github/e2e/playwright/screenshots`
diff --git a/e2e/playwright/mock_oauth_server/index.ts b/e2e/playwright/mock_oauth_server/index.ts
new file mode 100644
index 0000000..dbf49f1
--- /dev/null
+++ b/e2e/playwright/mock_oauth_server/index.ts
@@ -0,0 +1,28 @@
+require('dotenv').config();
+
+import {ExpiryAlgorithms, makeOAuthServer} from './mock_oauth_server';
+
+const mockOAuthAccessToken = process.env.MOCK_OAUTH_ACCESS_TOKEN;
+if (!mockOAuthAccessToken) {
+    console.error('Please provide an OAuth access token to use');
+    process.exit(0);
+}
+
+const defaultAuthorizePrefix = '/login/oauth' // Used by GitHub
+const authorizeURLPrefix = process.env.OAUTH_AUTHORIZE_URL_PREFIX || defaultAuthorizePrefix;
+
+const mattermostSiteURL = process.env.MM_SERVICESETTINGS_SITEURL || 'http://localhost:8065';
+const pluginId = process.env.MM_PLUGIN_ID || 'github';
+
+const app = makeOAuthServer({
+    authorizeURLPrefix,
+    mattermostSiteURL,
+    mockOAuthAccessToken,
+    pluginId,
+    expiryAlgorithm: ExpiryAlgorithms.NO_EXPIRY,
+});
+
+const port = process.env.OAUTH_SERVER_PORT || 8080;
+app.listen(port, () => {
+    console.log(`Mock OAuth server listening on port ${port}`);
+});
diff --git a/e2e/playwright/mock_oauth_server/mock_oauth_server.ts b/e2e/playwright/mock_oauth_server/mock_oauth_server.ts
new file mode 100644
index 0000000..e8f2947
--- /dev/null
+++ b/e2e/playwright/mock_oauth_server/mock_oauth_server.ts
@@ -0,0 +1,52 @@
+import express from 'express';
+
+export enum ExpiryAlgorithms {
+    NO_EXPIRY = 'no_expiry',
+}
+
+export type OAuthServerOptions = {
+    mattermostSiteURL: string;
+    pluginId: string;
+    mockOAuthAccessToken: string;
+    authorizeURLPrefix: string;
+    expiryAlgorithm: ExpiryAlgorithms;
+}
+
+export const makeOAuthServer = ({
+    mattermostSiteURL,
+    pluginId,
+    mockOAuthAccessToken,
+    authorizeURLPrefix,
+    expiryAlgorithm,
+}: OAuthServerOptions): express.Express => {
+    if (!mockOAuthAccessToken) {
+        throw new Error(`MockOAuthServer: Please provide an OAuth access token to use`);
+    }
+
+    if (expiryAlgorithm !== ExpiryAlgorithms.NO_EXPIRY) {
+        throw new Error(`MockOAuthServer: Unsupported OAuth token expiry algorithm: ${expiryAlgorithm}`);
+    }
+
+    const app = express();
+
+    const oauthRouter = express.Router();
+
+    oauthRouter.get('/authorize', function (req, res) {
+        const query = req.url.split('?')[1];
+        res.redirect(`${mattermostSiteURL}/plugins/${pluginId}/oauth/complete?${query}&code=1234`);
+    });
+
+    oauthRouter.post('/access_token', function (req, res) {
+        const token = {
+            access_token: mockOAuthAccessToken,
+            token_type: 'bearer',
+            expiry: '0001-01-01T00:00:00Z',
+        };
+
+        res.json(token);
+    });
+
+    app.use(authorizeURLPrefix, oauthRouter);
+
+    return app;
+}
diff --git a/e2e/playwright/mock_oauth_server/package-lock.json b/e2e/playwright/mock_oauth_server/package-lock.json
new file mode 100644
index 0000000..998bb07
--- /dev/null
+++ b/e2e/playwright/mock_oauth_server/package-lock.json
@@ -0,0 +1,1499 @@
+{
+  "name": "oauth_server",
+  "version": "1.0.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "oauth_server",
+      "version": "1.0.0",
+      "license": "ISC",
+      "dependencies": {
+        "dotenv": "^16.0.3",
+        "express": "^4.18.2"
+      },
+      "devDependencies": {
+        "@types/express": "^4.17.17",
+        "ts-node": "^10.9.1",
+        "typescript": "^5.0.2"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.4.14",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+      "dev": true
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+      "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+      "dev": true
+    },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.2",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
+      "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+      "dev": true,
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
+      "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/express": {
+      "version": "4.17.17",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
+      "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
+      "dev": true,
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "4.17.33",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz",
+      "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*"
+      }
+    },
+    "node_modules/@types/mime": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
+      "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "18.15.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
+      "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==",
+      "dev": true
+    },
+    "node_modules/@types/qs": {
+      "version": "6.9.7",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+      "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+      "dev": true
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
+      "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
+      "dev": true
+    },
+    "node_modules/@types/serve-static": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz",
+      "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/mime": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.8.2",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-walk": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.1",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+      "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.11.0",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+      "dependencies": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+    },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "16.0.3",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+      "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.18.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+      "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.1",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.5.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.2.0",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.11.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+      "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+      "dependencies": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dependencies": {
+        "function-bind": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.12.3",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "dependencies": {
+        "side-channel": "^1.0.4"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "node_modules/send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/serve-static": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "dependencies": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.18.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+    },
+    "node_modules/side-channel": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "dependencies": {
+        "call-bind": "^1.0.0",
+        "get-intrinsic": "^1.0.2",
+        "object-inspect": "^1.9.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/ts-node": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+      "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+      "dev": true,
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
+      "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=12.20"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "dev": true
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    }
+  },
+  "dependencies": {
+    "@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dev": true,
+      "requires": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      }
+    },
+    "@jridgewell/resolve-uri": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+      "dev": true
+    },
+    "@jridgewell/sourcemap-codec": {
+      "version": "1.4.14",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+      "dev": true
+    },
+    "@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dev": true,
+      "requires": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
+    "@tsconfig/node10": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+      "dev": true
+    },
+    "@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "dev": true
+    },
+    "@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "dev": true
+    },
+    "@tsconfig/node16": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+      "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+      "dev": true
+    },
+    "@types/body-parser": {
+      "version": "1.19.2",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
+      "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+      "dev": true,
+      "requires": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/connect": {
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
+      "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/express": {
+      "version": "4.17.17",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
+      "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
+      "dev": true,
+      "requires": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "@types/express-serve-static-core": {
+      "version": "4.17.33",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz",
+      "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*"
+      }
+    },
+    "@types/mime": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
+      "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "18.15.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
+      "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==",
+      "dev": true
+    },
+    "@types/qs": {
+      "version": "6.9.7",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+      "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+      "dev": true
+    },
+    "@types/range-parser": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
+      "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
+      "dev": true
+    },
+    "@types/serve-static": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz",
+      "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==",
+      "dev": true,
+      "requires": {
+        "@types/mime": "*",
+        "@types/node": "*"
+      }
+    },
+    "accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "requires": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      }
+    },
+    "acorn": {
+      "version": "8.8.2",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+      "dev": true
+    },
+    "acorn-walk": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+      "dev": true
+    },
+    "arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+    },
+    "body-parser": {
+      "version": "1.20.1",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+      "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+      "requires": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.11.0",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      }
+    },
+    "bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
+    },
+    "call-bind": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      }
+    },
+    "content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "requires": {
+        "safe-buffer": "5.2.1"
+      }
+    },
+    "content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
+    },
+    "cookie": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+    },
+    "create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+    },
+    "destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
+    },
+    "diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true
+    },
+    "dotenv": {
+      "version": "16.0.3",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+      "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
+    },
+    "express": {
+      "version": "4.18.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+      "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+      "requires": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.1",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.5.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.2.0",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.11.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      }
+    },
+    "finalhandler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "get-intrinsic": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+      "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.3"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
+    },
+    "http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "requires": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+    },
+    "make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+    },
+    "mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "requires": {
+        "mime-db": "1.52.0"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+    },
+    "negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+    },
+    "object-inspect": {
+      "version": "1.12.3",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
+    },
+    "on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+    },
+    "proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "requires": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "qs": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "requires": {
+        "side-channel": "^1.0.4"
+      }
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+      "requires": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.18.0"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+    },
+    "side-channel": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "get-intrinsic": "^1.0.2",
+        "object-inspect": "^1.9.0"
+      }
+    },
+    "statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
+    },
+    "toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
+    },
+    "ts-node": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+      "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+      "dev": true,
+      "requires": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      }
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "typescript": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
+      "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
+      "dev": true
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
+    },
+    "v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "dev": true
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
+    },
+    "yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true
+    }
+  }
+}
diff --git a/e2e/playwright/mock_oauth_server/package.json b/e2e/playwright/mock_oauth_server/package.json
new file mode 100644
index 0000000..6901db0
--- /dev/null
+++ b/e2e/playwright/mock_oauth_server/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "oauth_server",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "ts-node index.ts"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "dotenv": "^16.0.3",
+    "express": "^4.18.2"
+  },
+  "devDependencies": {
+    "@types/express": "^4.17.17",
+    "ts-node": "^10.9.1",
+    "typescript": "^5.0.2"
+  }
+}
diff --git a/e2e/playwright/package-lock.json b/e2e/playwright/package-lock.json
new file mode 100644
index 0000000..99f4a9e
--- /dev/null
+++ b/e2e/playwright/package-lock.json
@@ -0,0 +1,627 @@
+{
+  "name": "plugin-e2e-tests",
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "@types/body-parser": {
+      "version": "1.19.2",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
+      "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+      "dev": true,
+      "requires": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/connect": {
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
+      "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/express": {
+      "version": "4.17.17",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
+      "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
+      "dev": true,
+      "requires": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "@types/express-serve-static-core": {
+      "version": "4.17.33",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz",
+      "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*"
+      }
+    },
+    "@types/mime": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
+      "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "18.15.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
+      "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==",
+      "dev": true
+    },
+    "@types/pg": {
+      "version": "8.6.6",
+      "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz",
+      "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "pg-protocol": "*",
+        "pg-types": "^2.2.0"
+      }
+    },
+    "@types/qs": {
+      "version": "6.9.7",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+      "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+      "dev": true
+    },
+    "@types/range-parser": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
+      "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
+      "dev": true
+    },
+    "@types/serve-static": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz",
+      "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==",
+      "dev": true,
+      "requires": {
+        "@types/mime": "*",
+        "@types/node": "*"
+      }
+    },
+    "accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "requires": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+    },
+    "body-parser": {
+      "version": "1.20.1",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+      "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+      "requires": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.11.0",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      }
+    },
+    "buffer-writer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+      "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
+    },
+    "bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
+    },
+    "call-bind": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      }
+    },
+    "content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "requires": {
+        "safe-buffer": "5.2.1"
+      }
+    },
+    "content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
+    },
+    "cookie": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+    },
+    "destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
+    },
+    "dotenv": {
+      "version": "16.0.3",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+      "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+      "dev": true
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
+    },
+    "express": {
+      "version": "4.18.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+      "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+      "requires": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.1",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.5.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.2.0",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.11.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      }
+    },
+    "finalhandler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "get-intrinsic": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+      "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.3"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
+    },
+    "http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "requires": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+    },
+    "mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "requires": {
+        "mime-db": "1.52.0"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+    },
+    "negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+    },
+    "object-inspect": {
+      "version": "1.12.3",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
+    },
+    "on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "packet-reader": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+      "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+    },
+    "pg": {
+      "version": "8.10.0",
+      "resolved": "https://registry.npmjs.org/pg/-/pg-8.10.0.tgz",
+      "integrity": "sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==",
+      "requires": {
+        "buffer-writer": "2.0.0",
+        "packet-reader": "1.0.0",
+        "pg-connection-string": "^2.5.0",
+        "pg-pool": "^3.6.0",
+        "pg-protocol": "^1.6.0",
+        "pg-types": "^2.1.0",
+        "pgpass": "1.x"
+      }
+    },
+    "pg-connection-string": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
+      "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
+    },
+    "pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
+    },
+    "pg-pool": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz",
+      "integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ=="
+    },
+    "pg-protocol": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
+      "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
+    },
+    "pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "requires": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      }
+    },
+    "pgpass": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+      "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+      "requires": {
+        "split2": "^4.1.0"
+      }
+    },
+    "postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
+    },
+    "postgres-bytea": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+      "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
+    },
+    "postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
+    },
+    "postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "requires": {
+        "xtend": "^4.0.0"
+      }
+    },
+    "proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "requires": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "qs": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "requires": {
+        "side-channel": "^1.0.4"
+      }
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+      "requires": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.18.0"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+    },
+    "side-channel": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "get-intrinsic": "^1.0.2",
+        "object-inspect": "^1.9.0"
+      }
+    },
+    "split2": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+      "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ=="
+    },
+    "statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
+    },
+    "toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "typescript": {
+      "version": "4.9.5",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+      "dev": true
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+    }
+  }
+}
diff --git a/e2e/playwright/package.json b/e2e/playwright/package.json
new file mode 100644
index 0000000..c04ba94
--- /dev/null
+++ b/e2e/playwright/package.json
@@ -0,0 +1,20 @@
+{
+  "name": "plugin-e2e-tests",
+  "scripts": {
+    "test": "PW_SLOMO=200 npm run test --prefix ../../../mattermost-server/e2e-tests/playwright -- --project=chrome --config='../../../mattermost-plugin-github/e2e/playwright/playwright.config.ts'",
+    "test-ci": "PW_HEADLESS=true npm test",
+    "test-slomo": "npm run test-slomo --prefix ../../../mattermost-server/e2e-tests/playwright -- --project=chrome --config='../../../mattermost-plugin-github/e2e/playwright/playwright.config.ts",
+    "debug": "npm test -- --debug",
+    "show-report": "npm run show-report --prefix ../../../mattermost-server/e2e-tests/playwright"
+  },
+  "devDependencies": {
+    "@types/express": "^4.17.17",
+    "@types/pg": "^8.6.6",
+    "dotenv": "^16.0.3",
+    "typescript": "^4.9.5"
+  },
+  "dependencies": {
+    "express": "^4.18.2",
+    "pg": "^8.10.0"
+  }
+}
diff --git a/e2e/playwright/playwright.config.ts b/e2e/playwright/playwright.config.ts
new file mode 100644
index 0000000..6447bda
--- /dev/null
+++ b/e2e/playwright/playwright.config.ts
@@ -0,0 +1,8 @@
+import dotenv from 'dotenv';
+dotenv.config({path: `${__dirname}/.env`});
+
+import testConfig from '@e2e-test.playwright-config';
+
+testConfig.testDir = __dirname + '/tests';
+
+export default testConfig;
diff --git a/e2e/playwright/setup-environment b/e2e/playwright/setup-environment
new file mode 100755
index 0000000..cf273c0
--- /dev/null
+++ b/e2e/playwright/setup-environment
@@ -0,0 +1,17 @@
+cd ../../../
+git clone https://github.com/mattermost/mattermost-server.git
+cd mattermost-server
+
+source ~/.nvm/nvm.sh
+nvm install
+
+cd webapp
+npm i
+
+cd ../e2e-tests/playwright
+
+npm i
+npx playwright install --with-deps
+
+cd ../../../mattermost-plugin-github/e2e/playwright
+npm i
diff --git a/e2e/playwright/support/init_mock_oauth_server.ts b/e2e/playwright/support/init_mock_oauth_server.ts
new file mode 100644
index 0000000..276930f
--- /dev/null
+++ b/e2e/playwright/support/init_mock_oauth_server.ts
@@ -0,0 +1,28 @@
+import {ExpiryAlgorithms, makeOAuthServer} from '../mock_oauth_server/mock_oauth_server';
+
+const mockOAuthAccessToken = process.env.PLUGIN_E2E_MOCK_OAUTH_TOKEN;
+if (!mockOAuthAccessToken) {
+    console.error('Please provide an OAuth access token to use via env var PLUGIN_E2E_MOCK_OAUTH_TOKEN');
+    process.exit(1);
+}
+
+export const runOAuthServer = async () => {
+    const defaultAuthorizePrefix = '/login/oauth' // Used by GitHub
+    const authorizeURLPrefix = process.env.OAUTH_AUTHORIZE_URL_PREFIX || defaultAuthorizePrefix;
+
+    const mattermostSiteURL = process.env.MM_SERVICESETTINGS_SITEURL || 'http://localhost:8065';
+    const pluginId = process.env.MM_PLUGIN_ID || 'github';
+
+    const app = makeOAuthServer({
+        authorizeURLPrefix,
+        mattermostSiteURL,
+        mockOAuthAccessToken,
+        pluginId,
+        expiryAlgorithm: ExpiryAlgorithms.NO_EXPIRY,
+    });
+
+    const port = process.env.OAUTH_SERVER_PORT || 8080;
+    app.listen(port, () => {
+        console.log(`Mock OAuth server listening on port ${port}`);
+    });
+};
diff --git a/e2e/playwright/support/init_test.ts b/e2e/playwright/support/init_test.ts
new file mode 100644
index 0000000..b6c21d9
--- /dev/null
+++ b/e2e/playwright/support/init_test.ts
@@ -0,0 +1,110 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import path from 'node:path';
+import fs from 'node:fs';
+
+import {test} from '@e2e-support/test_fixture';
+import {cleanUpBotDMs} from './utils';
+
+import {clearKVStoreForPlugin} from './kv';
+import {DeepPartial} from '@mattermost/types/utilities';
+import {AdminConfig} from '@mattermost/types/config';
+
+import {runOAuthServer} from './init_mock_oauth_server';
+
+const pluginDistPath = path.join(__dirname, '../../../dist');
+const pluginId = 'github';
+
+// # Clear plugin's KV store
+test.beforeAll(async () => {
+    await clearKVStoreForPlugin(pluginId);
+    await runOAuthServer();
+});
+
+// # Upload plugin
+test.beforeEach(async ({pw}) => {
+    const files = await fs.promises.readdir(pluginDistPath);
+    const bundle = files.find((fname) => fname.endsWith('.tar.gz'));
+    if (!bundle) {
+        throw new Error('Failed to find plugin bundle in dist folder');
+    }
+
+    const bundlePath = path.join(pluginDistPath, bundle);
+    const {adminClient} = await pw.getAdminClient();
+
+    await adminClient.uploadPluginX(bundlePath, true);
+    await adminClient.enablePlugin(pluginId);
+});
+
+// # Clear bot DM channel
+test.beforeEach(async ({pw}) => {
+    const {adminClient, adminUser} = await pw.getAdminClient();
+    await cleanUpBotDMs(adminClient, adminUser!.id, pluginId);
+});
+
+type GithubPluginSettings = {
+    connecttoprivatebydefault: string | null;
+    enablecodepreview: string;
+    enableleftsidebar: boolean;
+    enableprivaterepo: boolean | null;
+    enablewebhookeventlogging: boolean;
+    encryptionkey: string;
+    enterprisebaseurl: string;
+    enterpriseuploadurl: string;
+    githuboauthclientid: string;
+    githuboauthclientsecret: string;
+    githuborg: string | null;
+    usepreregisteredapplication: boolean;
+    webhooksecret: string;
+}
+
+const githubConfig: GithubPluginSettings = {
+    githuboauthclientid: '',
+    githuboauthclientsecret: '',
+
+    connecttoprivatebydefault: null,
+    enablecodepreview: 'public',
+    enableleftsidebar: true,
+    enableprivaterepo: null,
+    enablewebhookeventlogging: false,
+    encryptionkey: '',
+    enterprisebaseurl: '',
+    enterpriseuploadurl: '',
+    githuborg: null,
+    usepreregisteredapplication: false,
+    webhooksecret: '',
+};
+
+// # Set plugin settings
+test.beforeAll(async ({pw}) => {
+    const {adminClient} = await pw.getAdminClient();
+
+    const config = await adminClient.getConfig();
+    const newConfig: DeepPartial<AdminConfig> = {
+        PluginSettings: {
+            ...config.PluginSettings,
+            Plugins: {
+                ...config.PluginSettings.Plugins,
+                [pluginId]: githubConfig as any,
+            },
+        },
+    };
+
+    await adminClient.patchConfig(newConfig);
+});
+
+// # Log in
+test.beforeEach(async ({pw, pages, page}) => {
+    const {adminClient, adminUser} = await pw.getAdminClient();
+    if (!adminUser) {
+        throw new Error('Failed to get admin user');
+    }
+
+    await adminClient.patchConfig({
+        ServiceSettings: {
+            EnableTutorial: false,
+            EnableOnboardingFlow: false,
+        },
+    });
+});
diff --git a/e2e/playwright/support/kv.ts b/e2e/playwright/support/kv.ts
new file mode 100644
index 0000000..fd9a098
--- /dev/null
+++ b/e2e/playwright/support/kv.ts
@@ -0,0 +1,26 @@
+import {Client} from 'pg';
+
+export function getClient() {
+    const client = new Client({
+        host: 'localhost',
+        user: 'mmuser',
+        password: 'mostest',
+        database: 'mattermost_test',
+    });
+
+    client.connect();
+    client.end = client.end.bind(client);
+    return client;
+}
+
+export const clearKVStoreForPlugin = async (pluginId: string) => {
+    const client = getClient();
+
+    // avoid clearing bot user id
+    const botUserKey = 'mmi_botid';
+
+    const query = 'DELETE from PluginKeyValueStore WHERE pkey != $1 AND pluginid = $2';
+
+    await client.query(query, [botUserKey, pluginId])
+        .finally(client.end);
+}
diff --git a/e2e/playwright/support/utils.ts b/e2e/playwright/support/utils.ts
new file mode 100644
index 0000000..366826a
--- /dev/null
+++ b/e2e/playwright/support/utils.ts
@@ -0,0 +1,66 @@
+import path from 'node:path';
+
+import type {Page} from '@playwright/test';
+
+import {ChannelsPage} from '@e2e-support/ui/pages';
+import {UserProfile} from '@mattermost/types/users';
+import Client4 from '@mattermost/client/client4';
+
+const SCREENSHOTS_DIR = path.join(__dirname, '../screenshots');
+
+export const DEFAULT_WAIT_MILLIS = 500;
+
+export const fillTextField = async (name: string, value: string, page: Page) => {
+    await page.getByTestId(`${name}input`).fill(value);
+}
+
+export const submitDialog = async (page: Page) => {
+    await page.click('#interactiveDialogSubmit');
+}
+
+export const postMessage = async (message: string, c: ChannelsPage, page: Page) => {
+    await c.postMessage(message);
+    await page.getByTestId('SendMessageButton').click();
+};
+
+export const clickPostAction = async (name: string, c: ChannelsPage) => {
+    // TODO we need to wait for the next post to come up, since this opening a new tab and OAuth redirect can take an undeterminate
+    // https://mattermost.atlassian.net/browse/MM-51906
+
+    const postElement = await c.getLastPost();
+    await postElement.container.getByText(name).last().click();
+};
+
+export const getLastPostText = async (c: ChannelsPage, page: Page): Promise<string> => {
+    await page.waitForTimeout(DEFAULT_WAIT_MILLIS);
+
+    const post = await c.getLastPost();
+    const postId = await post.getId();
+
+    const locatorId = `#post_${postId} .post-message`;
+    return page.locator(locatorId).innerText();
+}
+
+export const screenshot = async(name: string, page: Page) => {
+    await page.screenshot({path: path.join(SCREENSHOTS_DIR, name + '.png')});
+    console.log(`Created screenshot ${name}`);
+}
+
+export const cleanUpBotDMs = async (client: Client4, userId: UserProfile['id'], botUsername: string) => {
+    const bot = await client.getUserByUsername(botUsername);
+
+    const userIds = [userId, bot.id];
+    const channel = await client.createDirectChannel(userIds);
+    const posts = await client.getPosts(channel.id);
+
+    const deletePostPromises = Object.keys(posts.posts).map(client.deletePost);
+    await Promise.all(deletePostPromises);
+}
+
+export const getSlackAttachmentLocatorId = (postId: string) => {
+    return `#post_${postId} .attachment__body`;
+}
+
+export const getPostMessageLocatorId = (postId: string) => {
+    return `#post_${postId} .post-message`;
+}
diff --git a/e2e/playwright/tests/github_plugin.spec.ts b/e2e/playwright/tests/github_plugin.spec.ts
new file mode 100644
index 0000000..25ef906
--- /dev/null
+++ b/e2e/playwright/tests/github_plugin.spec.ts
@@ -0,0 +1,170 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+// ***************************************************************
+// - [#] indicates a test step (e.g. # Go to a page)
+// - [*] indicates an assertion (e.g. * Check the title)
+// ***************************************************************
+
+import {expect, test} from '@e2e-support/test_fixture';
+
+import '../support/init_test';
+
+import {
+    fillTextField,
+    postMessage,
+    submitDialog,
+    clickPostAction,
+    screenshot,
+    getSlackAttachmentLocatorId,
+    getPostMessageLocatorId,
+    DEFAULT_WAIT_MILLIS,
+} from '../support/utils';
+
+const GITHUB_CONNECT_LINK = '/plugins/github/oauth/connect';
+const TEST_CLIENT_ID = 'a'.repeat(20);
+const TEST_CLIENT_SECRET = 'b'.repeat(40);
+
+test('/github setup', async ({pw, pages, page: originalPage}) => {
+    // # Log in
+    const {adminUser} = await pw.getAdminClient();
+    const {page} = await pw.testBrowser.login(adminUser);
+    await originalPage.close();
+
+    // # Navigate to Channels
+    const c = new pages.ChannelsPage(page);
+    await c.goto();
+
+    // # Run setup command
+    await postMessage('/github setup', c, page);
+
+    // # Go to github bot DM channel
+    const teamName = page.url().split('/')[3];
+    await c.goto(teamName, 'messages/@github');
+
+    // # Go through prompts of setup flow
+    let choices: string[] = [
+        'Continue',
+        "I'll do it myself",
+        'No',
+        'Continue',
+        'Continue',
+    ];
+
+    let i = 0;
+    for (const choice of choices) {
+        i++;
+        await page.waitForTimeout(DEFAULT_WAIT_MILLIS);
+        await screenshot(`post_action_before_${i}`, page);
+        await clickPostAction(choice, c);
+        await screenshot(`post_action_after_${i}`, page);
+    }
+
+    // # Fill out interactive dialog for GitHub client id and client secret
+    await fillTextField('client_id', TEST_CLIENT_ID, page);
+    await fillTextField('client_secret', TEST_CLIENT_SECRET, page);
+    await submitDialog(page);
+
+    await page.waitForTimeout(DEFAULT_WAIT_MILLIS);
+
+    const post = await c.getLastPost();
+    const postId = await post.getId();
+    const locatorId = getSlackAttachmentLocatorId(postId);
+
+    const text = await page.locator(locatorId).innerText();
+    expect(text).toEqual('Go here to connect your account.');
+
+    await screenshot('github_setup/show_connect_link', page);
+
+    // * Verify connect link has correct URL
+    const connectLinkLocator = `${locatorId} a`;
+    const href = await page.locator(connectLinkLocator).getAttribute('href');
+    expect(href).toMatch(GITHUB_CONNECT_LINK);
+
+    await screenshot(`connect_click_before`, page);
+    await page.click(connectLinkLocator);
+    await screenshot(`connect_click_after`, page);
+
+    // # Say no to "Create a webhook"
+    await screenshot(`webhook_question_before`, page);
+    await clickPostAction('No', c);
+    await screenshot(`webhook_question_aftrt`, page);
+
+    // # Say no to "Broadcast to channel"
+    await screenshot(`broadcast_question_before`, page);
+    await clickPostAction('Not now', c);
+    await screenshot(`broadcast_question_after`, page);
+
+    await screenshot('github_setup/done', page);
+    await page.close();
+});
+
+test('/github connect', async ({pw, pages, page: originalPage}) => {
+    // # Log in
+    const {adminUser} = await pw.getAdminClient();
+    const {page} = await pw.testBrowser.login(adminUser);
+    await originalPage.close();
+
+    // # Navigate to Channels
+    const c = new pages.ChannelsPage(page);
+    await c.goto();
+
+    // # Run connect command
+    await postMessage('/github connect', c, page);
+    await page.waitForTimeout(DEFAULT_WAIT_MILLIS);
+
+    let post = await c.getLastPost();
+    let postId = await post.getId();
+    let locatorId = getPostMessageLocatorId(postId);
+
+    let text = await page.locator(locatorId).innerText();
+    expect(text).toEqual('Click here to link your GitHub account.');
+
+    await screenshot('github_connect/show_connect_link', page);
+
+    // * Verify connect link has correct URL
+    const connectLinkLocator = `${locatorId} a`;
+    const href = await page.locator(connectLinkLocator).getAttribute('href');
+    expect(href).toMatch(GITHUB_CONNECT_LINK);
+
+    await page.click(connectLinkLocator);
+    await screenshot('github_connect/after_clicking_connect_link', page);
+
+    // # Go to github bot DM channel
+    const teamName = page.url().split('/')[3];
+    await c.goto(teamName, 'messages/@github');
+    await page.waitForTimeout(DEFAULT_WAIT_MILLIS);
+
+    post = await c.getLastPost();
+    postId = await post.getId();
+    locatorId = getPostMessageLocatorId(postId);
+
+    text = await page.locator(locatorId).innerText();
+    expect(text).toContain('Welcome to the Mattermost GitHub Plugin!');
+
+    await screenshot('github_connect/after_navigate_to_github_plugin', page);
+    await page.close();
+});
+
+test('/github issue create', async ({pw, pages, page: originalPage}) => {
+    // # Log in
+    const {adminUser} = await pw.getAdminClient();
+    const {page} = await pw.testBrowser.login(adminUser);
+    await originalPage.close();
+
+    // # Navigate to Channels
+    const c = new pages.ChannelsPage(page);
+    await c.goto();
+
+    // # Run create command
+    await postMessage('/github issue create', c, page);
+    await page.waitForTimeout(DEFAULT_WAIT_MILLIS);
+
+    await screenshot('github_issue_create/ran_create_command', page);
+
+    // * Check that Create Issue modal is shown
+    await expect(page.getByRole('heading', {
+        name: 'Create GitHub Issue'
+    })).toBeVisible();
+    await page.close();
+});
diff --git a/e2e/playwright/tsconfig.json b/e2e/playwright/tsconfig.json
new file mode 100644
index 0000000..7e0c256
--- /dev/null
+++ b/e2e/playwright/tsconfig.json
@@ -0,0 +1,19 @@
+{
+  "compilerOptions": {
+      "esModuleInterop": true,
+      "strict": true,
+      "strictNullChecks": true,
+      "skipLibCheck": true,
+      "noEmit": true,
+      "baseUrl": ".",
+      "paths": {
+          "@mattermost/client/*": ["../../../mattermost-server/webapp/platform/client/lib/*"],
+          "@mattermost/types/*": ["../../../mattermost-server/webapp/platform/types/lib/*"],
+          "@e2e-support/*": ["../../../mattermost-server/e2e-tests/playwright/support/*"],
+          "@e2e-test.config": ["../../../mattermost-server/e2e-tests/playwright/test.config.ts"],
+          "@e2e-test.playwright-config": ["../../../mattermost-server/e2e-tests/playwright/playwright.config.ts"],
+          "@playwright/test": ["../../../mattermost-server/e2e-tests/playwright/node_modules/@playwright/test"],
+      }
+  },
+  "include": ["./**/*"]
+}
diff --git a/server/plugin/permalinks_test.go b/server/plugin/permalinks_test.go
index 0927652..98b8135 100644
--- a/server/plugin/permalinks_test.go
+++ b/server/plugin/permalinks_test.go
@@ -48,7 +48,34 @@ func TestGetReplacements(t *testing.T) {
 					},
 				},
 			},
-		}, {
+		},
+		{
+			name:            "basic link with relative path",
+			input:           "start https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/../../../authentication.go#L15-L22 lorem ipsum",
+			numReplacements: 1,
+			replacements: []replacement{
+				{
+					index: 6,
+					word:  "https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/../../../authentication.go#L15-L22",
+					permalinkInfo: struct {
+						haswww string
+						commit string
+						user   string
+						repo   string
+						path   string
+						line   string
+					}{
+						haswww: "",
+						commit: "cbb25838a61872b624ac512556d7bc932486a64c",
+						line:   "L15-L22",
+						path:   "authentication.go",
+						user:   "mattermost",
+						repo:   "mattermost-server",
+					},
+				},
+			},
+		},
+		{
 			name:            "duplicate expansions",
 			input:           "start https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22 lorem ipsum https://github.com/mattermost/mattermost-server/blob/cbb25838a61872b624ac512556d7bc932486a64c/app/authentication.go#L15-L22 lorem ipsum",
 			numReplacements: 2,
diff --git a/webapp/tests/setup.js b/webapp/tests/setup.js
deleted file mode 100644
index ff32c4e..0000000
--- a/webapp/tests/setup.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import 'mattermost-webapp/tests/setup';