summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml17
-rw-r--r--.gitignore5
-rw-r--r--backend/browser.py (renamed from plugin_loader/browser.py)0
-rw-r--r--backend/injector.py (renamed from plugin_loader/injector.py)22
-rw-r--r--backend/loader.py162
-rw-r--r--backend/main.py85
-rw-r--r--backend/plugin.py (renamed from plugin_loader/plugin.py)21
-rw-r--r--backend/utilities.py (renamed from plugin_loader/utilities.py)0
-rw-r--r--frontend/.gitignore1
-rwxr-xr-xfrontend/.husky/pre-commit4
-rw-r--r--frontend/.prettierrc.js9
-rw-r--r--frontend/package-lock.json2323
-rw-r--r--frontend/package.json27
-rw-r--r--frontend/rollup.config.js16
-rw-r--r--frontend/src/index.ts16
-rw-r--r--frontend/src/logger.ts35
-rw-r--r--frontend/src/plugin-loader.ts131
-rw-r--r--frontend/src/tabs-hook.ts69
-rw-r--r--plugin_loader/loader.py191
-rw-r--r--plugin_loader/main.py144
-rw-r--r--plugin_loader/static/library.js71
-rw-r--r--plugin_loader/static/plugin_page.js98
-rw-r--r--plugin_loader/static/styles.css3
-rw-r--r--plugin_loader/templates/plugin_view.html76
-rw-r--r--requirements.txt3
25 files changed, 2921 insertions, 608 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8642a307..537729c5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -18,21 +18,32 @@ jobs:
steps:
- name: 🧰 Checkout
uses: actions/checkout@v3
+
+ - name: 💎 Set up NodeJS 17
+ uses: actions/setup-node@v3
+ with:
+ node-version: 17
- name: 🐍 Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- - name: ⬇️ Install dependencies
+ - name: ⬇️ Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install pyinstaller
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
-
+
+ - name: ⬇️ Install NodeJS dependencies
+ run: |
+ cd frontend
+ npm i
+ npm run build
+
- name: 🛠️ Build
run: |
- pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./plugin_loader/static:/static --add-data ./plugin_loader/templates:/templates ./plugin_loader/*.py
+ pyinstaller --noconfirm --onefile --name "Decky" --add-data ./backend/static:/static ./backend/*.py
- name: ⬆️ Upload package
uses: actions/upload-artifact@v2
diff --git a/.gitignore b/.gitignore
index dff282fb..4ab519c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -149,4 +149,7 @@ dmypy.json
.pytype/
# Cython debug symbols
-cython_debug/ \ No newline at end of file
+cython_debug/
+
+# static files are built
+backend/static
diff --git a/plugin_loader/browser.py b/backend/browser.py
index ffec26b3..ffec26b3 100644
--- a/plugin_loader/browser.py
+++ b/backend/browser.py
diff --git a/plugin_loader/injector.py b/backend/injector.py
index c2157472..16ced852 100644
--- a/plugin_loader/injector.py
+++ b/backend/injector.py
@@ -1,10 +1,11 @@
#Injector code from https://github.com/SteamDeckHomebrew/steamdeck-ui-inject. More info on how it works there.
-from aiohttp import ClientSession
-from logging import debug, getLogger
from asyncio import sleep
+from logging import debug, getLogger
from traceback import format_exc
+from aiohttp import ClientSession
+
BASE_ADDRESS = "http://localhost:8080"
logger = getLogger("Injector")
@@ -21,7 +22,7 @@ class Tab:
async def open_websocket(self):
self.client = ClientSession()
self.websocket = await self.client.ws_connect(self.ws_url)
-
+
async def listen_for_message(self):
async for message in self.websocket:
yield message
@@ -43,13 +44,10 @@ class Tab:
"awaitPromise": run_async
}
})
+
await self.client.close()
return res
-
- async def get_steam_resource(self, url):
- res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True)
- return res["result"]["result"]["value"]
-
+
def __repr__(self):
return self.title
@@ -77,20 +75,20 @@ async def get_tab(tab_name):
tab = next((i for i in tabs if i.title == tab_name), None)
if not tab:
raise ValueError(f"Tab {tab_name} not found")
- return tab
+ return tab
async def inject_to_tab(tab_name, js, run_async=False):
tab = await get_tab(tab_name)
return await tab.evaluate_js(js, run_async)
-async def tab_has_element(tab_name, element_name):
+async def tab_has_global_var(tab_name, var_name):
try:
tab = await get_tab(tab_name)
except ValueError:
return False
- res = await tab.evaluate_js(f"document.getElementById('{element_name}') != null", False)
-
+ res = await tab.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False)
+
if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]:
return False
diff --git a/backend/loader.py b/backend/loader.py
new file mode 100644
index 00000000..e68842e2
--- /dev/null
+++ b/backend/loader.py
@@ -0,0 +1,162 @@
+from asyncio import Queue
+from logging import getLogger
+from os import listdir, path
+from pathlib import Path
+from time import time
+from traceback import print_exc
+
+from aiohttp import web
+from aiohttp_jinja2 import template
+from genericpath import exists
+from watchdog.events import FileSystemEventHandler
+from watchdog.observers.polling import PollingObserver as Observer
+
+from injector import inject_to_tab
+from plugin import PluginWrapper
+
+
+class FileChangeHandler(FileSystemEventHandler):
+ def __init__(self, queue, plugin_path) -> None:
+ super().__init__()
+ self.logger = getLogger("file-watcher")
+ self.plugin_path = plugin_path
+ self.queue = queue
+
+ def maybe_reload(self, src_path):
+ plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0]
+ self.logger.info(path.join(self.plugin_path, plugin_dir, "plugin.json"))
+ if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")):
+ self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
+
+ def on_created(self, event):
+ src_path = event.src_path
+ if "__pycache__" in src_path:
+ return
+
+ # check to make sure this isn't a directory
+ if path.isdir(src_path):
+ return
+
+ # get the directory name of the plugin so that we can find its "main.py" and reload it; the
+ # file that changed is not necessarily the one that needs to be reloaded
+ self.logger.debug(f"file created: {src_path}")
+ self.maybe_reload(src_path)
+
+ def on_modified(self, event):
+ src_path = event.src_path
+ if "__pycache__" in src_path:
+ return
+
+ # check to make sure this isn't a directory
+ if path.isdir(src_path):
+ return
+
+ # get the directory name of the plugin so that we can find its "main.py" and reload it; the
+ # file that changed is not necessarily the one that needs to be reloaded
+ self.logger.debug(f"file modified: {src_path}")
+ self.maybe_reload(src_path)
+
+class Loader:
+ def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None:
+ self.loop = loop
+ self.logger = getLogger("Loader")
+ self.plugin_path = plugin_path
+ self.logger.info(f"plugin_path: {self.plugin_path}")
+ self.plugins = {}
+ self.import_plugins()
+
+ if live_reload:
+ self.reload_queue = Queue()
+ self.observer = Observer()
+ self.observer.schedule(FileChangeHandler(self.reload_queue, plugin_path), self.plugin_path, recursive=True)
+ self.observer.start()
+ self.loop.create_task(self.handle_reloads())
+
+ server_instance.add_routes([
+ web.get("/plugins", self.handle_plugins),
+ web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
+ web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call),
+ web.post("/methods/{method_name}", self.handle_server_method_call)
+ ])
+
+ def handle_plugins(self, request):
+ plugins = list(map(lambda kv: dict([("name", kv[0])]), self.plugins.items()))
+ return web.json_response(plugins)
+
+ def handle_frontend_bundle(self, request):
+ plugin = self.plugins[request.match_info["plugin_name"]]
+
+ with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.frontend_bundle), 'r') as bundle:
+ return web.Response(text=bundle.read(), content_type="application/javascript")
+
+ def import_plugin(self, file, plugin_directory, refresh=False):
+ try:
+ plugin = PluginWrapper(file, plugin_directory, self.plugin_path)
+ if plugin.name in self.plugins:
+ if not "debug" in plugin.flags and refresh:
+ self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded")
+ return
+ else:
+ self.plugins[plugin.name].stop()
+ self.plugins.pop(plugin.name, None)
+ if plugin.passive:
+ self.logger.info(f"Plugin {plugin.name} is passive")
+ self.plugins[plugin.name] = plugin.start()
+ self.logger.info(f"Loaded {plugin.name}")
+ if refresh:
+ self.loop.create_task(self.reload_frontend_plugin(plugin.name))
+ except Exception as e:
+ self.logger.error(f"Could not load {file}. {e}")
+ print_exc()
+
+ async def reload_frontend_plugin(self, name):
+ await inject_to_tab("SP", f"window.DeckyPluginLoader?.loadPlugin('{name}')")
+
+ def import_plugins(self):
+ self.logger.info(f"import plugins from {self.plugin_path}")
+
+ directories = [i for i in listdir(self.plugin_path) if path.isdir(path.join(self.plugin_path, i)) and path.isfile(path.join(self.plugin_path, i, "plugin.json"))]
+ for directory in directories:
+ self.logger.info(f"found plugin: {directory}")
+ self.import_plugin(path.join(self.plugin_path, directory, "main.py"), directory)
+
+ async def handle_reloads(self):
+ while True:
+ args = await self.reload_queue.get()
+ self.import_plugin(*args)
+
+ async def handle_server_method_call(self, request):
+ method_name = request.match_info["method_name"]
+ method_info = await request.json()
+ args = method_info["args"]
+
+ res = {}
+ try:
+ r = await self.utilities.util_methods[method_name](**args)
+ res["result"] = r
+ res["success"] = True
+ except Exception as e:
+ res["result"] = str(e)
+ res["success"] = False
+
+ return web.json_response(res)
+
+ async def handle_plugin_method_call(self, request):
+ res = {}
+ plugin = self.plugins[request.match_info["plugin_name"]]
+ method_name = request.match_info["method_name"]
+
+ method_info = await request.json()
+ args = method_info["args"]
+
+ try:
+ if method_name.startswith("_"):
+ raise RuntimeError("Tried to call private method")
+
+ res["result"] = await plugin.execute_method(method_name, args)
+ res["success"] = True
+ except Exception as e:
+ res["result"] = str(e)
+ res["success"] = False
+
+ return web.json_response(res)
diff --git a/backend/main.py b/backend/main.py
new file mode 100644
index 00000000..7e10165e
--- /dev/null
+++ b/backend/main.py
@@ -0,0 +1,85 @@
+from logging import DEBUG, INFO, basicConfig, getLogger
+from os import getenv
+
+CONFIG = {
+ "plugin_path": getenv("PLUGIN_PATH", "/home/deck/homebrew/plugins"),
+ "chown_plugin_path": getenv("CHOWN_PLUGIN_PATH", "1") == "1",
+ "server_host": getenv("SERVER_HOST", "127.0.0.1"),
+ "server_port": int(getenv("SERVER_PORT", "1337")),
+ "live_reload": getenv("LIVE_RELOAD", "1") == "1",
+ "log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")],
+ "store_url": getenv("STORE_URL", "https://sdh.tzatzi.me/browse")
+}
+
+basicConfig(level=CONFIG["log_level"], format="[%(module)s][%(levelname)s]: %(message)s")
+
+from asyncio import get_event_loop, sleep
+from json import dumps, loads
+from os import path
+from subprocess import Popen
+
+import aiohttp_cors
+from aiohttp.web import Application, run_app, static
+from aiohttp_jinja2 import setup as jinja_setup
+from jinja2 import FileSystemLoader
+
+from browser import PluginBrowser
+from injector import get_tab, inject_to_tab, tab_has_global_var
+from loader import Loader
+from utilities import Utilities
+
+logger = getLogger("Main")
+
+async def chown_plugin_dir(_):
+ Popen(["chown", "-R", "deck:deck", CONFIG["plugin_path"]])
+ Popen(["chmod", "-R", "555", CONFIG["plugin_path"]])
+
+class PluginManager:
+ def __init__(self) -> None:
+ self.loop = get_event_loop()
+ self.web_app = Application()
+ self.cors = aiohttp_cors.setup(self.web_app, defaults={
+ "https://steamloopback.host": aiohttp_cors.ResourceOptions(expose_headers="*",
+ allow_headers="*")
+ })
+ self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
+ self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, CONFIG["store_url"])
+ self.utilities = Utilities(self)
+
+ jinja_setup(self.web_app)
+ self.web_app.on_startup.append(self.inject_javascript)
+
+ if CONFIG["chown_plugin_path"] == True:
+ self.web_app.on_startup.append(chown_plugin_dir)
+
+ self.loop.create_task(self.loader_reinjector())
+
+ self.loop.set_exception_handler(self.exception_handler)
+
+ for route in list(self.web_app.router.routes()):
+ self.cors.add(route)
+
+ def exception_handler(self, loop, context):
+ if context["message"] == "Unclosed connection":
+ return
+ loop.default_exception_handler(context)
+
+ async def loader_reinjector(self):
+ while True:
+ await sleep(1)
+ if not await tab_has_global_var("SP", "DeckyPluginLoader"):
+ logger.info("Plugin loader isn't present in Steam anymore, reinjecting...")
+ await self.inject_javascript()
+
+ async def inject_javascript(self, request=None):
+ try:
+ await inject_to_tab("SP", open(path.join(path.dirname(__file__), "./static/plugin-loader.iife.js"), "r").read(), True)
+ except:
+ logger.info("Failed to inject JavaScript into tab")
+ pass
+
+ def run(self):
+ return run_app(self.web_app, host=CONFIG["server_host"], port=CONFIG["server_port"], loop=self.loop, access_log=None)
+
+if __name__ == "__main__":
+ PluginManager().run()
diff --git a/plugin_loader/plugin.py b/backend/plugin.py
index 30626058..db335225 100644
--- a/plugin_loader/plugin.py
+++ b/backend/plugin.py
@@ -1,11 +1,17 @@
-from importlib.util import spec_from_file_location, module_from_spec
-from asyncio import get_event_loop, new_event_loop, set_event_loop, start_unix_server, open_unix_connection, sleep, Lock
-from os import path, setuid
-from json import loads, dumps, load
-from time import time
+from asyncio import (Lock, get_event_loop, new_event_loop,
+ open_unix_connection, set_event_loop, sleep,
+ start_unix_server)
+from concurrent.futures import ProcessPoolExecutor
+from importlib.util import module_from_spec, spec_from_file_location
+from json import dumps, load, loads
from multiprocessing import Process
-from signal import signal, SIGINT
+from os import path, setuid
+from signal import SIGINT, signal
from sys import exit
+from time import time
+
+from injector import inject_to_tab
+
class PluginWrapper:
def __init__(self, file, plugin_directory, plugin_path) -> None:
@@ -20,8 +26,7 @@ class PluginWrapper:
self.name = json["name"]
self.author = json["author"]
- self.main_view_html = json["main_view_html"]
- self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else ""
+ self.frontend_bundle = json["frontend_bundle"]
self.flags = json["flags"]
self.passive = not path.isfile(self.file)
diff --git a/plugin_loader/utilities.py b/backend/utilities.py
index 39f9ca55..39f9ca55 100644
--- a/plugin_loader/utilities.py
+++ b/backend/utilities.py
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 00000000..c2658d7d
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/frontend/.husky/pre-commit b/frontend/.husky/pre-commit
new file mode 100755
index 00000000..84796e1d
--- /dev/null
+++ b/frontend/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+cd frontend && npm run lint
diff --git a/frontend/.prettierrc.js b/frontend/.prettierrc.js
new file mode 100644
index 00000000..01727ff9
--- /dev/null
+++ b/frontend/.prettierrc.js
@@ -0,0 +1,9 @@
+module.exports = {
+ semi: true,
+ trailingComma: 'all',
+ singleQuote: true,
+ printWidth: 120,
+ tabWidth: 2,
+ endOfLine: 'auto',
+ plugins: [require('prettier-plugin-import-sort')],
+};
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 00000000..eea9146b
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,2323 @@
+{
+ "name": "decky_frontend",
+ "version": "0.0.1",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "decky_frontend",
+ "version": "0.0.1",
+ "license": "GPLV2",
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^22.0.0",
+ "@rollup/plugin-node-resolve": "^13.3.0",
+ "@rollup/plugin-typescript": "^8.3.2",
+ "husky": "^8.0.1",
+ "import-sort-style-module": "^6.0.0",
+ "prettier": "^2.6.2",
+ "prettier-plugin-import-sort": "^0.0.7",
+ "rollup": "^2.71.1"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+ "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
+ "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.16.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz",
+ "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz",
+ "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.1.0",
+ "@babel/code-frame": "^7.16.7",
+ "@babel/generator": "^7.17.10",
+ "@babel/helper-compilation-targets": "^7.17.10",
+ "@babel/helper-module-transforms": "^7.17.7",
+ "@babel/helpers": "^7.17.9",
+ "@babel/parser": "^7.17.10",
+ "@babel/template": "^7.16.7",
+ "@babel/traverse": "^7.17.10",
+ "@babel/types": "^7.17.10",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.1",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz",
+ "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.17.10",
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz",
+ "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.17.10",
+ "@babel/helper-validator-option": "^7.16.7",
+ "browserslist": "^4.20.2",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
+ "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.16.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz",
+ "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.16.7",
+ "@babel/types": "^7.17.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
+ "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.16.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
+ "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.16.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.17.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
+ "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.16.7",
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/helper-simple-access": "^7.17.7",
+ "@babel/helper-split-export-declaration": "^7.16.7",
+ "@babel/helper-validator-identifier": "^7.16.7",
+ "@babel/template": "^7.16.7",
+ "@babel/traverse": "^7.17.3",
+ "@babel/types": "^7.17.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.17.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
+ "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.17.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
+ "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.16.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
+ "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz",
+ "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz",
+ "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.16.7",
+ "@babel/traverse": "^7.17.9",
+ "@babel/types": "^7.17.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz",
+ "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.16.7",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz",
+ "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
+ "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.16.7",
+ "@babel/parser": "^7.16.7",
+ "@babel/types": "^7.16.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz",
+ "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.16.7",
+ "@babel/generator": "^7.17.10",
+ "@babel/helper-environment-visitor": "^7.16.7",
+ "@babel/helper-function-name": "^7.17.9",
+ "@babel/helper-hoist-variables": "^7.16.7",
+ "@babel/helper-split-export-declaration": "^7.16.7",
+ "@babel/parser": "^7.17.10",
+ "@babel/types": "^7.17.10",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz",
+ "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.16.7",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+ "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz",
+ "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz",
+ "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz",
+ "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.11.tgz",
+ "integrity": "sha512-RllI476aSMsxzeI9TtlSMoNTgHDxEmnl6GkkHwhr0vdL8W+0WuesyI8Vd3rBOfrwtPXbPxdT9ADJdiOKgzxPQA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs": {
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.0.tgz",
+ "integrity": "sha512-Ktvf2j+bAO+30awhbYoCaXpBcyPmJbaEUYClQns/+6SNCYFURbvBiNbWgHITEsIgDDWCDUclWRKEuf8cwZCFoQ==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.1",
+ "glob": "^7.1.6",
+ "is-reference": "^1.2.1",
+ "magic-string": "^0.25.7",
+ "resolve": "^1.17.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.68.0"
+ }
+ },
+ "node_modules/@rollup/plugin-node-resolve": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz",
+ "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "@types/resolve": "1.17.1",
+ "deepmerge": "^4.2.2",
+ "is-builtin-module": "^3.1.0",
+ "is-module": "^1.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.42.0"
+ }
+ },
+ "node_modules/@rollup/plugin-typescript": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.2.tgz",
+ "integrity": "sha512-MtgyR5LNHZr3GyN0tM7gNO9D0CS+Y+vflS4v/PHmrX17JCkHUYKvQ5jN5o3cz1YKllM3duXUqu3yOHwMPUxhDg==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "resolve": "^1.17.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.14.0",
+ "tslib": "*",
+ "typescript": ">=3.7.0"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0"
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
+ },
+ "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ },
+ "node_modules/@types/estree": {
+ "version": "0.0.51",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
+ "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "17.0.31",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz",
+ "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==",
+ "dev": true
+ },
+ "node_modules/@types/resolve": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.20.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz",
+ "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001332",
+ "electron-to-chromium": "^1.4.118",
+ "escalade": "^3.1.1",
+ "node-releases": "^2.0.3",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/builtin-modules": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
+ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caller-callsite": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/caller-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+ "dev": true,
+ "dependencies": {
+ "caller-callsite": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+ "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001339",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001339.tgz",
+ "integrity": "sha512-Es8PiVqCe+uXdms0Gu5xP5PF2bxLR7OBp3wUzUnuO7OHzhOfCyg3hdiGWVPVxhiuniOzng+hTc1u3fEQ0TlkSQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
+ "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+ "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+ "dev": true,
+ "dependencies": {
+ "import-fresh": "^2.0.0",
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.13.1",
+ "parse-json": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
+ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.137",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz",
+ "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/find-line-column": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/find-line-column/-/find-line-column-0.5.2.tgz",
+ "integrity": "sha1-2wAjj/hoVRoYLnShA0FtKVqYyMo=",
+ "dev": true
+ },
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/husky": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz",
+ "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==",
+ "dev": true,
+ "bin": {
+ "husky": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+ "dev": true,
+ "dependencies": {
+ "caller-path": "^2.0.0",
+ "resolve-from": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/import-fresh/node_modules/resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/import-sort": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort/-/import-sort-6.0.0.tgz",
+ "integrity": "sha512-XUwSQMGAGmcW/wfshFE0gXgb1NPF6ibbQD6wDr3KRDykZf/lZj0jf58Bwa02xNb8EE59oz7etFe9OHnJocUW5Q==",
+ "dev": true,
+ "dependencies": {
+ "detect-newline": "^2.1.0",
+ "import-sort-parser": "^6.0.0",
+ "import-sort-style": "^6.0.0",
+ "is-builtin-module": "^3.0.0",
+ "resolve": "^1.8.1"
+ }
+ },
+ "node_modules/import-sort-config": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-config/-/import-sort-config-6.0.0.tgz",
+ "integrity": "sha512-FJpF2F3+30JXqH1rJKeajxoSCHCueai3/0ntDN4y3GJL5pjnLDt/VjCy5FzjH7u0NHnllL/zVEf1wfmsVxJlPQ==",
+ "dev": true,
+ "dependencies": {
+ "cosmiconfig": "^5.0.5",
+ "find-root": "^1.0.0",
+ "minimatch": "^3.0.4",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "node_modules/import-sort-parser": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-parser/-/import-sort-parser-6.0.0.tgz",
+ "integrity": "sha512-H5L+d6HnqHvThB0GmAA3/43Sv74oCwL0iMk3/ixOv0LRJ69rCyHXeG/+UadMHrD2FefEmgPIWboEPAG7gsQrkA==",
+ "dev": true
+ },
+ "node_modules/import-sort-parser-babylon": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-parser-babylon/-/import-sort-parser-babylon-6.0.0.tgz",
+ "integrity": "sha512-NyShTiNhTh4Vy7kJUVe6CuvOaQAzzfSIT72wtp3CzGjz8bHjNj59DCAjncuviicmDOgVAgmLuSh1WMcLYAMWGg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.2.2",
+ "@babel/parser": "^7.0.0-beta.54",
+ "@babel/traverse": "^7.0.0-beta.54",
+ "@babel/types": "^7.0.0-beta.54",
+ "find-line-column": "^0.5.2"
+ }
+ },
+ "node_modules/import-sort-parser-typescript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-parser-typescript/-/import-sort-parser-typescript-6.0.0.tgz",
+ "integrity": "sha512-pgxnr3I156DonupQriNsgDb2zJN9TxrqCCIN1rwT/6SDO1rkJb+a0fjqshCjlgacTSA92oPAp1eAwmQUeZi3dw==",
+ "dev": true,
+ "dependencies": {
+ "typescript": "^3.2.4"
+ }
+ },
+ "node_modules/import-sort-parser-typescript/node_modules/typescript": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
+ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/import-sort-style": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-style/-/import-sort-style-6.0.0.tgz",
+ "integrity": "sha512-z0H5PKs7YoDeKxNYXv2AA1mjjZFY07fjeNCXUdTM3ymJtWeeEoTm8CQkFm2l+KPZoMczIvdwzJpWkkOamBnsPw==",
+ "dev": true
+ },
+ "node_modules/import-sort-style-module": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-style-module/-/import-sort-style-module-6.0.0.tgz",
+ "integrity": "sha512-Oxd256EVt6TAgawhIDuKnNHWumzHMHFWhVncBBvlHVnx69B4GP/Gu4Xo+gjxtqSEKEvam5ajUkNvnsXLDMDjKg==",
+ "dev": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "node_modules/is-builtin-module": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz",
+ "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==",
+ "dev": true,
+ "dependencies": {
+ "builtin-modules": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
+ "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-directory": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
+ "node_modules/is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz",
+ "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==",
+ "dev": true
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "dependencies": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
+ "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-import-sort": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-import-sort/-/prettier-plugin-import-sort-0.0.7.tgz",
+ "integrity": "sha512-O0KlUSq+lwvh+UiN3wZDT6wWkf7TNxTVv2/XXE5KqpRNbFJq3nRg2ftzBYFFO8QGpdWIrOB0uCTCtFjIxmVKQw==",
+ "dev": true,
+ "dependencies": {
+ "import-sort": "^6.0.0",
+ "import-sort-config": "^6.0.0",
+ "import-sort-parser-babylon": "^6.0.0",
+ "import-sort-parser-typescript": "^6.0.0"
+ },
+ "peerDependencies": {
+ "prettier": ">= 2.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
+ "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.8.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.71.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.71.1.tgz",
+ "integrity": "sha512-lMZk3XfUBGjrrZQpvPSoXcZSfKcJ2Bgn+Z0L1MoW2V8Wh7BVM+LOBJTPo16yul2MwL59cXedzW1ruq3rCjSRgw==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/typescript": {
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
+ "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ }
+ },
+ "dependencies": {
+ "@ampproject/remapping": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+ "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
+ "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.16.7"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz",
+ "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==",
+ "dev": true
+ },
+ "@babel/core": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz",
+ "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==",
+ "dev": true,
+ "requires": {
+ "@ampproject/remapping": "^2.1.0",
+ "@babel/code-frame": "^7.16.7",
+ "@babel/generator": "^7.17.10",
+ "@babel/helper-compilation-targets": "^7.17.10",
+ "@babel/helper-module-transforms": "^7.17.7",
+ "@babel/helpers": "^7.17.9",
+ "@babel/parser": "^7.17.10",
+ "@babel/template": "^7.16.7",
+ "@babel/traverse": "^7.17.10",
+ "@babel/types": "^7.17.10",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.1",
+ "semver": "^6.3.0"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz",
+ "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.17.10",
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "jsesc": "^2.5.1"
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz",
+ "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.17.10",
+ "@babel/helper-validator-option": "^7.16.7",
+ "browserslist": "^4.20.2",
+ "semver": "^6.3.0"
+ }
+ },
+ "@babel/helper-environment-visitor": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
+ "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.16.7"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz",
+ "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.16.7",
+ "@babel/types": "^7.17.0"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
+ "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.16.7"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
+ "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.16.7"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.17.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
+ "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.16.7",
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/helper-simple-access": "^7.17.7",
+ "@babel/helper-split-export-declaration": "^7.16.7",
+ "@babel/helper-validator-identifier": "^7.16.7",
+ "@babel/template": "^7.16.7",
+ "@babel/traverse": "^7.17.3",
+ "@babel/types": "^7.17.0"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.17.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
+ "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.17.0"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
+ "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.16.7"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
+ "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
+ "dev": true
+ },
+ "@babel/helper-validator-option": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz",
+ "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==",
+ "dev": true
+ },
+ "@babel/helpers": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz",
+ "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.16.7",
+ "@babel/traverse": "^7.17.9",
+ "@babel/types": "^7.17.0"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz",
+ "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.16.7",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz",
+ "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.16.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
+ "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.16.7",
+ "@babel/parser": "^7.16.7",
+ "@babel/types": "^7.16.7"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz",
+ "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.16.7",
+ "@babel/generator": "^7.17.10",
+ "@babel/helper-environment-visitor": "^7.16.7",
+ "@babel/helper-function-name": "^7.17.9",
+ "@babel/helper-hoist-variables": "^7.16.7",
+ "@babel/helper-split-export-declaration": "^7.16.7",
+ "@babel/parser": "^7.17.10",
+ "@babel/types": "^7.17.10",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz",
+ "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.16.7",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "@jridgewell/gen-mapping": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+ "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz",
+ "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==",
+ "dev": true
+ },
+ "@jridgewell/set-array": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz",
+ "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==",
+ "dev": true
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz",
+ "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==",
+ "dev": true
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.11.tgz",
+ "integrity": "sha512-RllI476aSMsxzeI9TtlSMoNTgHDxEmnl6GkkHwhr0vdL8W+0WuesyI8Vd3rBOfrwtPXbPxdT9ADJdiOKgzxPQA==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "@rollup/plugin-commonjs": {
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.0.tgz",
+ "integrity": "sha512-Ktvf2j+bAO+30awhbYoCaXpBcyPmJbaEUYClQns/+6SNCYFURbvBiNbWgHITEsIgDDWCDUclWRKEuf8cwZCFoQ==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.1",
+ "glob": "^7.1.6",
+ "is-reference": "^1.2.1",
+ "magic-string": "^0.25.7",
+ "resolve": "^1.17.0"
+ }
+ },
+ "@rollup/plugin-node-resolve": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz",
+ "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "@types/resolve": "1.17.1",
+ "deepmerge": "^4.2.2",
+ "is-builtin-module": "^3.1.0",
+ "is-module": "^1.0.0",
+ "resolve": "^1.19.0"
+ }
+ },
+ "@rollup/plugin-typescript": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.2.tgz",
+ "integrity": "sha512-MtgyR5LNHZr3GyN0tM7gNO9D0CS+Y+vflS4v/PHmrX17JCkHUYKvQ5jN5o3cz1YKllM3duXUqu3yOHwMPUxhDg==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "resolve": "^1.17.0"
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "dependencies": {
+ "@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ }
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.51",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
+ "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "17.0.31",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz",
+ "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==",
+ "dev": true
+ },
+ "@types/resolve": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "browserslist": {
+ "version": "4.20.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz",
+ "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001332",
+ "electron-to-chromium": "^1.4.118",
+ "escalade": "^3.1.1",
+ "node-releases": "^2.0.3",
+ "picocolors": "^1.0.0"
+ }
+ },
+ "builtin-modules": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
+ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
+ "dev": true
+ },
+ "caller-callsite": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+ "dev": true,
+ "requires": {
+ "callsites": "^2.0.0"
+ }
+ },
+ "caller-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+ "dev": true,
+ "requires": {
+ "caller-callsite": "^2.0.0"
+ }
+ },
+ "callsites": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+ "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+ "dev": true
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001339",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001339.tgz",
+ "integrity": "sha512-Es8PiVqCe+uXdms0Gu5xP5PF2bxLR7OBp3wUzUnuO7OHzhOfCyg3hdiGWVPVxhiuniOzng+hTc1u3fEQ0TlkSQ==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
+ "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "cosmiconfig": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+ "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+ "dev": true,
+ "requires": {
+ "import-fresh": "^2.0.0",
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.13.1",
+ "parse-json": "^4.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "deepmerge": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+ "dev": true
+ },
+ "detect-newline": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
+ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
+ "dev": true
+ },
+ "electron-to-chromium": {
+ "version": "1.4.137",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz",
+ "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==",
+ "dev": true
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "find-line-column": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/find-line-column/-/find-line-column-0.5.2.tgz",
+ "integrity": "sha1-2wAjj/hoVRoYLnShA0FtKVqYyMo=",
+ "dev": true
+ },
+ "find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "husky": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz",
+ "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+ "dev": true,
+ "requires": {
+ "caller-path": "^2.0.0",
+ "resolve-from": "^3.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true
+ }
+ }
+ },
+ "import-sort": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort/-/import-sort-6.0.0.tgz",
+ "integrity": "sha512-XUwSQMGAGmcW/wfshFE0gXgb1NPF6ibbQD6wDr3KRDykZf/lZj0jf58Bwa02xNb8EE59oz7etFe9OHnJocUW5Q==",
+ "dev": true,
+ "requires": {
+ "detect-newline": "^2.1.0",
+ "import-sort-parser": "^6.0.0",
+ "import-sort-style": "^6.0.0",
+ "is-builtin-module": "^3.0.0",
+ "resolve": "^1.8.1"
+ }
+ },
+ "import-sort-config": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-config/-/import-sort-config-6.0.0.tgz",
+ "integrity": "sha512-FJpF2F3+30JXqH1rJKeajxoSCHCueai3/0ntDN4y3GJL5pjnLDt/VjCy5FzjH7u0NHnllL/zVEf1wfmsVxJlPQ==",
+ "dev": true,
+ "requires": {
+ "cosmiconfig": "^5.0.5",
+ "find-root": "^1.0.0",
+ "minimatch": "^3.0.4",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "import-sort-parser": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-parser/-/import-sort-parser-6.0.0.tgz",
+ "integrity": "sha512-H5L+d6HnqHvThB0GmAA3/43Sv74oCwL0iMk3/ixOv0LRJ69rCyHXeG/+UadMHrD2FefEmgPIWboEPAG7gsQrkA==",
+ "dev": true
+ },
+ "import-sort-parser-babylon": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-parser-babylon/-/import-sort-parser-babylon-6.0.0.tgz",
+ "integrity": "sha512-NyShTiNhTh4Vy7kJUVe6CuvOaQAzzfSIT72wtp3CzGjz8bHjNj59DCAjncuviicmDOgVAgmLuSh1WMcLYAMWGg==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.2.2",
+ "@babel/parser": "^7.0.0-beta.54",
+ "@babel/traverse": "^7.0.0-beta.54",
+ "@babel/types": "^7.0.0-beta.54",
+ "find-line-column": "^0.5.2"
+ }
+ },
+ "import-sort-parser-typescript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-parser-typescript/-/import-sort-parser-typescript-6.0.0.tgz",
+ "integrity": "sha512-pgxnr3I156DonupQriNsgDb2zJN9TxrqCCIN1rwT/6SDO1rkJb+a0fjqshCjlgacTSA92oPAp1eAwmQUeZi3dw==",
+ "dev": true,
+ "requires": {
+ "typescript": "^3.2.4"
+ },
+ "dependencies": {
+ "typescript": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
+ "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
+ "dev": true
+ }
+ }
+ },
+ "import-sort-style": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-style/-/import-sort-style-6.0.0.tgz",
+ "integrity": "sha512-z0H5PKs7YoDeKxNYXv2AA1mjjZFY07fjeNCXUdTM3ymJtWeeEoTm8CQkFm2l+KPZoMczIvdwzJpWkkOamBnsPw==",
+ "dev": true
+ },
+ "import-sort-style-module": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/import-sort-style-module/-/import-sort-style-module-6.0.0.tgz",
+ "integrity": "sha512-Oxd256EVt6TAgawhIDuKnNHWumzHMHFWhVncBBvlHVnx69B4GP/Gu4Xo+gjxtqSEKEvam5ajUkNvnsXLDMDjKg==",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-builtin-module": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz",
+ "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==",
+ "dev": true,
+ "requires": {
+ "builtin-modules": "^3.0.0"
+ }
+ },
+ "is-core-module": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
+ "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-directory": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+ "dev": true
+ },
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
+ "is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*"
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ },
+ "json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "dev": true
+ },
+ "magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "requires": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node-releases": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz",
+ "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ },
+ "prettier": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
+ "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
+ "dev": true
+ },
+ "prettier-plugin-import-sort": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-import-sort/-/prettier-plugin-import-sort-0.0.7.tgz",
+ "integrity": "sha512-O0KlUSq+lwvh+UiN3wZDT6wWkf7TNxTVv2/XXE5KqpRNbFJq3nRg2ftzBYFFO8QGpdWIrOB0uCTCtFjIxmVKQw==",
+ "dev": true,
+ "requires": {
+ "import-sort": "^6.0.0",
+ "import-sort-config": "^6.0.0",
+ "import-sort-parser-babylon": "^6.0.0",
+ "import-sort-parser-typescript": "^6.0.0"
+ }
+ },
+ "resolve": {
+ "version": "1.22.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
+ "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.8.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "rollup": {
+ "version": "2.71.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.71.1.tgz",
+ "integrity": "sha512-lMZk3XfUBGjrrZQpvPSoXcZSfKcJ2Bgn+Z0L1MoW2V8Wh7BVM+LOBJTPo16yul2MwL59cXedzW1ruq3rCjSRgw==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ },
+ "tslib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+ "dev": true,
+ "peer": true
+ },
+ "typescript": {
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
+ "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
+ "dev": true,
+ "peer": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 00000000..a6598320
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "decky_frontend",
+ "version": "0.0.1",
+ "private": true,
+ "license": "GPLV2",
+ "scripts": {
+ "prepare": "cd .. && husky install frontend/.husky",
+ "build": "rollup -c",
+ "lint": "prettier -c src",
+ "format": "prettier -c src -w"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^22.0.0",
+ "@rollup/plugin-node-resolve": "^13.3.0",
+ "@rollup/plugin-typescript": "^8.3.2",
+ "husky": "^8.0.1",
+ "import-sort-style-module": "^6.0.0",
+ "prettier": "^2.6.2",
+ "prettier-plugin-import-sort": "^0.0.7",
+ "rollup": "^2.71.1"
+ },
+ "importSort": {
+ ".js, .jsx, .ts, .tsx": {
+ "style": "module"
+ }
+ }
+}
diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js
new file mode 100644
index 00000000..26b8670a
--- /dev/null
+++ b/frontend/rollup.config.js
@@ -0,0 +1,16 @@
+import commonjs from '@rollup/plugin-commonjs';
+import resolve from '@rollup/plugin-node-resolve';
+import typescript from '@rollup/plugin-typescript';
+
+
+/** @type {import('rollup').RollupOptions} */
+const options = {
+ input: 'src/index.ts',
+ output: {
+ file: '../backend/static/plugin-loader.iife.js',
+ format: 'iife',
+ },
+ plugins: [commonjs(), resolve(), typescript()]
+}
+
+export default options
diff --git a/frontend/src/index.ts b/frontend/src/index.ts
new file mode 100644
index 00000000..390b83c9
--- /dev/null
+++ b/frontend/src/index.ts
@@ -0,0 +1,16 @@
+import PluginLoader from './plugin-loader';
+
+declare global {
+ interface Window {
+ DeckyPluginLoader?: PluginLoader;
+ }
+}
+
+if (window.DeckyPluginLoader) {
+ window.DeckyPluginLoader?.dismountAll();
+}
+
+window.DeckyPluginLoader = new PluginLoader();
+setTimeout(async () => {
+ window.DeckyPluginLoader?.loadAllPlugins();
+}, 5000);
diff --git a/frontend/src/logger.ts b/frontend/src/logger.ts
new file mode 100644
index 00000000..9eb515a3
--- /dev/null
+++ b/frontend/src/logger.ts
@@ -0,0 +1,35 @@
+export const log = (name: string, ...args: any[]) => {
+ console.log(
+ `%c Decky %c ${name} %c`,
+ 'background: #16a085; color: black;',
+ 'background: #1abc9c; color: black;',
+ 'background: transparent;',
+ ...args,
+ );
+};
+
+export const error = (name: string, ...args: any[]) => {
+ console.log(
+ `%c Decky %c ${name} %c`,
+ 'background: #16a085; color: black;',
+ 'background: #FF0000;',
+ 'background: transparent;',
+ ...args,
+ );
+};
+
+class Logger {
+ constructor(private name: string) {
+ this.name = name;
+ }
+
+ log(...args: any[]) {
+ log(this.name, ...args);
+ }
+
+ debug(...args: any[]) {
+ log(this.name, ...args);
+ }
+}
+
+export default Logger;
diff --git a/frontend/src/plugin-loader.ts b/frontend/src/plugin-loader.ts
new file mode 100644
index 00000000..9a72ea84
--- /dev/null
+++ b/frontend/src/plugin-loader.ts
@@ -0,0 +1,131 @@
+import Logger from './logger';
+import TabsHook from './tabs-hook';
+
+interface Plugin {
+ title: any;
+ content: any;
+ icon: any;
+ onDismount?(): void;
+}
+
+class PluginLoader extends Logger {
+ private pluginInstances: Record<string, Plugin> = {};
+ private tabsHook: TabsHook;
+ private lock = 0;
+
+ constructor() {
+ super(PluginLoader.name);
+
+ this.log('Initialized');
+ this.tabsHook = new TabsHook();
+ }
+
+ dismountPlugin(name: string) {
+ this.log(`Dismounting ${name}`);
+ this.pluginInstances[name]?.onDismount?.();
+ delete this.pluginInstances[name];
+ this.tabsHook.removeById(name);
+ }
+
+ async loadAllPlugins() {
+ this.log('Loading all plugins');
+ const plugins = await (await fetch(`http://127.0.0.1:1337/plugins`)).json();
+ this.log('Received:', plugins);
+
+ return Promise.all(plugins.map((plugin) => this.loadPlugin(plugin.name)));
+ }
+
+ async loadPlugin(name) {
+ this.log('Loading Plugin:', name);
+
+ try {
+ while (this.lock === 1) {
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+ this.lock = 1;
+
+ if (this.pluginInstances[name]) {
+ this.dismountPlugin(name);
+ }
+
+ const response = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`);
+ const code = await response.text();
+
+ const pluginAPI = PluginLoader.createPluginAPI(name);
+ this.pluginInstances[name] = await eval(code)(pluginAPI);
+
+ const { title, icon, content } = this.pluginInstances[name];
+ this.tabsHook.add({
+ id: name,
+ title,
+ icon,
+ content,
+ });
+ } catch (e) {
+ console.error(e);
+ } finally {
+ this.lock = 0;
+ }
+ }
+
+ dismountAll() {
+ for (const name of Object.keys(this.pluginInstances)) {
+ this.dismountPlugin(name);
+ }
+ }
+
+ static createPluginAPI(pluginName) {
+ return {
+ async callServerMethod(methodName, args = {}) {
+ const response = await fetch(`http://127.0.0.1:1337/methods/${methodName}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(args),
+ });
+
+ return response.json();
+ },
+ async callPluginMethod(methodName, args = {}) {
+ const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ args,
+ }),
+ });
+
+ return response.json();
+ },
+ fetchNoCors(url, request: any = {}) {
+ let args = { method: 'POST', headers: {}, body: '' };
+ const req = { ...args, ...request, url, data: request.body };
+ return this.callServerMethod('http_request', req);
+ },
+ executeInTab(tab, runAsync, code) {
+ return this.callServerMethod('execute_in_tab', {
+ tab,
+ run_async: runAsync,
+ code,
+ });
+ },
+ injectCssIntoTab(tab, style) {
+ return this.callServerMethod('inject_css_into_tab', {
+ tab,
+ style,
+ });
+ },
+ removeCssFromTab(tab, cssId) {
+ return this.callServerMethod('remove_css_from_tab', {
+ tab,
+ css_id: cssId,
+ });
+ },
+ };
+ }
+}
+
+export default PluginLoader;
diff --git a/frontend/src/tabs-hook.ts b/frontend/src/tabs-hook.ts
new file mode 100644
index 00000000..17f41d91
--- /dev/null
+++ b/frontend/src/tabs-hook.ts
@@ -0,0 +1,69 @@
+import Logger from './logger';
+
+declare global {
+ interface Window {
+ __TABS_HOOK_INSTANCE: any;
+ }
+ interface Array<T> {
+ __filter: any;
+ }
+}
+
+const isTabsArray = (tabs) => {
+ const length = tabs.length;
+ return length === 7 && tabs[length - 1]?.key === 6 && tabs[length - 1]?.tab;
+};
+
+interface Tab {
+ id: string;
+ title: any;
+ content: any;
+ icon: any;
+}
+
+class TabsHook extends Logger {
+ // private keys = 7;
+ tabs: Tab[] = [];
+
+ constructor() {
+ super('TabsHook');
+
+ this.log('Initialized');
+ window.__TABS_HOOK_INSTANCE = this;
+
+ const self = this;
+
+ const filter = Array.prototype.__filter ?? Array.prototype.filter;
+ Array.prototype.__filter = filter;
+ Array.prototype.filter = function (...args) {
+ if (isTabsArray(this)) {
+ self.render(this);
+ }
+ // @ts-ignore
+ return filter.call(this, ...args);
+ };
+ }
+
+ add(tab: Tab) {
+ this.log('Adding tab', tab.id, 'to render array');
+ this.tabs.push(tab);
+ }
+
+ removeById(id: string) {
+ this.log('Removing tab', id);
+ this.tabs = this.tabs.filter((tab) => tab.id !== id);
+ }
+
+ render(existingTabs: any[]) {
+ for (const { title, icon, content, id } of this.tabs) {
+ existingTabs.push({
+ key: id,
+ title,
+ tab: icon,
+ panel: content,
+ });
+ }
+ }
+}
+
+export default TabsHook;
diff --git a/plugin_loader/loader.py b/plugin_loader/loader.py
deleted file mode 100644
index fea5f149..00000000
--- a/plugin_loader/loader.py
+++ /dev/null
@@ -1,191 +0,0 @@
-from aiohttp import web
-from aiohttp_jinja2 import template
-from watchdog.observers.polling import PollingObserver as Observer
-from watchdog.events import FileSystemEventHandler
-from asyncio import Queue
-from os import path, listdir
-from logging import getLogger
-from time import time
-
-from injector import get_tabs, get_tab
-from plugin import PluginWrapper
-from traceback import print_exc
-
-class FileChangeHandler(FileSystemEventHandler):
- def __init__(self, queue, plugin_path) -> None:
- super().__init__()
- self.logger = getLogger("file-watcher")
- self.plugin_path = plugin_path
- self.queue = queue
-
- def on_created(self, event):
- src_path = event.src_path
- if "__pycache__" in src_path:
- return
-
- # check to make sure this isn't a directory
- if path.isdir(src_path):
- return
-
- # get the directory name of the plugin so that we can find its "main.py" and reload it; the
- # file that changed is not necessarily the one that needs to be reloaded
- self.logger.debug(f"file created: {src_path}")
- rel_path = path.relpath(src_path, path.commonprefix([self.plugin_path, src_path]))
- plugin_dir = path.split(rel_path)[0]
- main_file_path = path.join(self.plugin_path, plugin_dir, "main.py")
- self.queue.put_nowait((main_file_path, plugin_dir, True))
-
- def on_modified(self, event):
- src_path = event.src_path
- if "__pycache__" in src_path:
- return
-
- # check to make sure this isn't a directory
- if path.isdir(src_path):
- return
-
- # get the directory name of the plugin so that we can find its "main.py" and reload it; the
- # file that changed is not necessarily the one that needs to be reloaded
- self.logger.debug(f"file modified: {src_path}")
- plugin_dir = path.split(path.relpath(src_path, path.commonprefix([self.plugin_path, src_path])))[0]
- self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
-
-class Loader:
- def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None:
- self.loop = loop
- self.logger = getLogger("Loader")
- self.plugin_path = plugin_path
- self.logger.info(f"plugin_path: {self.plugin_path}")
- self.plugins = {}
- self.callsigns = {}
- self.callsign_matches = {}
- self.import_plugins()
-
- if live_reload:
- self.reload_queue = Queue()
- self.observer = Observer()
- self.observer.schedule(FileChangeHandler(self.reload_queue, plugin_path), self.plugin_path, recursive=True)
- self.observer.start()
- self.loop.create_task(self.handle_reloads())
-
- server_instance.add_routes([
- web.get("/plugins/iframe", self.plugin_iframe_route),
- web.get("/plugins/load_main/{name}", self.load_plugin_main_view),
- web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route),
- web.get("/plugins/load_tile/{name}", self.load_plugin_tile_view),
- web.get("/steam_resource/{path:.+}", self.get_steam_resource)
- ])
-
- def import_plugin(self, file, plugin_directory, refresh=False):
- try:
- plugin = PluginWrapper(file, plugin_directory, self.plugin_path)
- if plugin.name in self.plugins:
- if not "debug" in plugin.flags and refresh:
- self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded")
- return
- else:
- self.plugins[plugin.name].stop()
- self.plugins.pop(plugin.name, None)
- self.callsigns.pop(self.callsign_matches[file], None)
- if plugin.passive:
- self.logger.info(f"Plugin {plugin.name} is passive")
- callsign = str(time())
- plugin.callsign = callsign
- self.plugins[plugin.name] = plugin.start()
- self.callsigns[callsign] = plugin
- self.callsign_matches[file] = callsign
- self.logger.info(f"Loaded {plugin.name}")
- except Exception as e:
- self.logger.error(f"Could not load {file}. {e}")
- print_exc()
- finally:
- if refresh:
- self.loop.create_task(self.refresh_iframe())
-
- def import_plugins(self):
- self.logger.info(f"import plugins from {self.plugin_path}")
-
- directories = [i for i in listdir(self.plugin_path) if path.isdir(path.join(self.plugin_path, i)) and path.isfile(path.join(self.plugin_path, i, "plugin.json"))]
- for directory in directories:
- self.logger.info(f"found plugin: {directory}")
- self.import_plugin(path.join(self.plugin_path, directory, "main.py"), directory)
-
- async def handle_reloads(self):
- while True:
- args = await self.reload_queue.get()
- self.import_plugin(*args)
-
- async def handle_plugin_method_call(self, callsign, method_name, **kwargs):
- if method_name.startswith("_"):
- raise RuntimeError("Tried to call private method")
- return await self.callsigns[callsign].execute_method(method_name, kwargs)
-
- async def get_steam_resource(self, request):
- tab = (await get_tabs())[0]
- try:
- return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html")
- except Exception as e:
- return web.Response(text=str(e), status=400)
-
- async def load_plugin_main_view(self, request):
- plugin = self.callsigns[request.match_info["name"]]
-
- # open up the main template
- with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), 'r') as template:
- template_data = template.read()
- # setup the main script, plugin, and pull in the template
- ret = f"""
- <script src="/static/library.js"></script>
- <script>const plugin_name = '{plugin.callsign}' </script>
- <base href="http://127.0.0.1:1337/plugins/plugin_resource/{plugin.callsign}/">
- {template_data}
- """
- return web.Response(text=ret, content_type="text/html")
-
- async def handle_sub_route(self, request):
- plugin = self.callsigns[request.match_info["name"]]
- route_path = request.match_info["path"]
- self.logger.info(path)
-
- ret = ""
-
- file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path)
- with open(file_path, 'r') as resource_data:
- ret = resource_data.read()
-
- return web.Response(text=ret)
-
- async def load_plugin_tile_view(self, request):
- plugin = self.callsigns[request.match_info["name"]]
-
- inner_content = ""
-
- # open up the tile template (if we have one defined)
- if hasattr(plugin, "tile_view_html"):
- with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.tile_view_html), 'r') as template:
- template_data = template.read()
- inner_content = template_data
-
- # setup the default template
- ret = f"""
- <html style="height: fit-content;">
- <head>
- <link rel="stylesheet" href="/static/styles.css">
- <script src="/static/library.js"></script>
- <script>const plugin_name = '{plugin.callsign}';</script>
- </head>
- <body style="height: fit-content; display: block;">
- {inner_content}
- </body>
- <html>
- """
- return web.Response(text=ret, content_type="text/html")
-
- @template('plugin_view.html')
- async def plugin_iframe_route(self, request):
- return {"plugins": self.plugins.values()}
-
- async def refresh_iframe(self):
- tab = await get_tab("QuickAccess")
- await tab.open_websocket()
- return await tab.evaluate_js("reloadIframe()", False)
diff --git a/plugin_loader/main.py b/plugin_loader/main.py
deleted file mode 100644
index 9ed30760..00000000
--- a/plugin_loader/main.py
+++ /dev/null
@@ -1,144 +0,0 @@
-from logging import getLogger, basicConfig, INFO, DEBUG, Filter, root
-from os import getenv
-
-CONFIG = {
- "plugin_path": getenv("PLUGIN_PATH", "/home/deck/homebrew/plugins"),
- "server_host": getenv("SERVER_HOST", "127.0.0.1"),
- "server_port": int(getenv("SERVER_PORT", "1337")),
- "live_reload": getenv("LIVE_RELOAD", "1") == "1",
- "log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")],
- "store_url": getenv("STORE_URL", "https://plugins.deckbrew.xyz"),
- "log_base_events": getenv("LOG_BASE_EVENTS", "0")=="1"
-}
-
-class NoBaseEvents(Filter):
- def filter(self, record):
- return not "asyncio" in record.name
-
-basicConfig(level=CONFIG["log_level"], format="[%(module)s][%(levelname)s]: %(message)s")
-for handler in root.handlers:
- if not CONFIG["log_base_events"]:
- handler.addFilter(NoBaseEvents())
-
-from aiohttp.web import Application, run_app, static
-from aiohttp_jinja2 import setup as jinja_setup
-from jinja2 import FileSystemLoader
-from os import path
-from asyncio import get_event_loop, sleep
-from json import loads, dumps
-from subprocess import Popen
-
-from loader import Loader
-from injector import inject_to_tab, get_tab, tab_has_element
-from utilities import Utilities
-from browser import PluginBrowser
-
-logger = getLogger("Main")
-from traceback import print_exc
-
-async def chown_plugin_dir(_):
- Popen(["chown", "-R", "deck:deck", CONFIG["plugin_path"]])
- Popen(["chmod", "-R", "555", CONFIG["plugin_path"]])
-
-class PluginManager:
- def __init__(self) -> None:
- self.loop = get_event_loop()
- self.web_app = Application()
- self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
- self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, CONFIG["store_url"])
- self.utilities = Utilities(self)
-
- jinja_setup(self.web_app, loader=FileSystemLoader(path.join(path.dirname(__file__), 'templates')))
- self.web_app.on_startup.append(self.inject_javascript)
- self.web_app.on_startup.append(chown_plugin_dir)
- self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
- self.loop.create_task(self.method_call_listener())
- self.loop.create_task(self.loader_reinjector())
-
- self.loop.set_exception_handler(self.exception_handler)
-
- def exception_handler(self, loop, context):
- if context["message"] == "Unclosed connection":
- return
- loop.default_exception_handler(context)
-
- async def loader_reinjector(self):
- finished_reinjection = False
- logger.info("Plugin loader isn't present in Steam anymore, reinjecting...")
- while True:
- await sleep(1)
- if not await tab_has_element("QuickAccess", "plugin_iframe"):
- logger.debug("Plugin loader isn't present in Steam anymore, reinjecting...")
- await self.inject_javascript()
- finished_reinjection = True
- elif finished_reinjection:
- finished_reinjection = False
- logger.info("Reinjecting successful!")
-
- self.loop.create_task(self.method_call_listener())
-
- async def inject_javascript(self, request=None):
- try:
- await inject_to_tab("QuickAccess", open(path.join(path.dirname(__file__), "static/library.js"), "r").read())
- await inject_to_tab("QuickAccess", open(path.join(path.dirname(__file__), "static/plugin_page.js"), "r").read())
- except:
- logger.info("Failed to inject JavaScript into tab")
- pass
-
- async def resolve_method_call(self, tab, call_id, response):
- try:
- r = dumps(response)
- except Exception as e:
- logger.error(response["result"])
- response["result"] = str(response["result"])
- r = response
- await tab._send_devtools_cmd({
- "id": 1,
- "method": "Runtime.evaluate",
- "params": {
- "expression": f"resolveMethodCall({call_id}, {r})",
- "userGesture": True
- }
- }, receive=False)
-
- async def handle_method_call(self, method, tab):
- res = {}
- try:
- if method["method"] == "plugin_method":
- res["result"] = await self.plugin_loader.handle_plugin_method_call(
- method["args"]["plugin_name"],
- method["args"]["method_name"],
- **method["args"]["args"]
- )
- res["success"] = True
- else:
- r = await self.utilities.util_methods[method["method"]](**method["args"])
- res["result"] = r
- res["success"] = True
- except Exception as e:
- res["result"] = str(e)
- res["success"] = False
- finally:
- await self.resolve_method_call(tab, method["id"], res)
-
- async def method_call_listener(self):
- while True:
- try:
- tab = await get_tab("QuickAccess")
- break
- except:
- await sleep(1)
- await tab.open_websocket()
- await tab._send_devtools_cmd({"id": 1, "method": "Runtime.discardConsoleEntries"})
- await tab._send_devtools_cmd({"id": 1, "method": "Runtime.enable"})
- async for message in tab.listen_for_message():
- data = message.json()
- if not "id" in data and data["method"] == "Runtime.consoleAPICalled" and data["params"]["type"] == "debug":
- method = loads(data["params"]["args"][0]["value"])
- self.loop.create_task(self.handle_method_call(method, tab))
-
- def run(self):
- return run_app(self.web_app, host=CONFIG["server_host"], port=CONFIG["server_port"], loop=self.loop, access_log=None)
-
-if __name__ == "__main__":
- PluginManager().run() \ No newline at end of file
diff --git a/plugin_loader/static/library.js b/plugin_loader/static/library.js
deleted file mode 100644
index 744cc77f..00000000
--- a/plugin_loader/static/library.js
+++ /dev/null
@@ -1,71 +0,0 @@
-class PluginEventTarget extends EventTarget { }
-method_call_ev_target = new PluginEventTarget();
-
-window.addEventListener("message", function(evt) {
- let ev = new Event(evt.data.call_id);
- ev.data = evt.data.result;
- method_call_ev_target.dispatchEvent(ev);
-}, false);
-
-async function call_server_method(method_name, arg_object={}) {
- let id = `${uuidv4()}`;
- console.debug(JSON.stringify({
- "id": id,
- "method": method_name,
- "args": arg_object
- }));
- return new Promise((resolve, reject) => {
- method_call_ev_target.addEventListener(`${id}`, function (event) {
- if (event.data.success) resolve(event.data.result);
- else reject(event.data.result);
- });
- });
-}
-
-// Source: https://stackoverflow.com/a/2117523 Thanks!
-function uuidv4() {
- return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
- (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
- );
-}
-
-async function fetch_nocors(url, request={}) {
- let args = { method: "POST", headers: {}, body: "" };
- request = {...args, ...request};
- request.url = url;
- request.data = request.body;
- delete request.body; //maintain api-compatibility with fetch
- return await call_server_method("http_request", request);
-}
-
-async function call_plugin_method(method_name, arg_object={}) {
- if (plugin_name == undefined)
- throw new Error("Plugin methods can only be called from inside plugins (duh)");
- return await call_server_method("plugin_method", {
- 'plugin_name': plugin_name,
- 'method_name': method_name,
- 'args': arg_object
- });
-}
-
-async function execute_in_tab(tab, run_async, code) {
- return await call_server_method("execute_in_tab", {
- 'tab': tab,
- 'run_async': run_async,
- 'code': code
- });
-}
-
-async function inject_css_into_tab(tab, style) {
- return await call_server_method("inject_css_into_tab", {
- 'tab': tab,
- 'style': style
- });
-}
-
-async function remove_css_from_tab(tab, css_id) {
- return await call_server_method("remove_css_from_tab", {
- 'tab': tab,
- 'css_id': css_id
- });
-} \ No newline at end of file
diff --git a/plugin_loader/static/plugin_page.js b/plugin_loader/static/plugin_page.js
deleted file mode 100644
index 0531f04e..00000000
--- a/plugin_loader/static/plugin_page.js
+++ /dev/null
@@ -1,98 +0,0 @@
-function reloadIframe() {
- document.getElementById("plugin_iframe").contentWindow.location.href = "http://127.0.0.1:1337/plugins/iframe";
-}
-
-function resolveMethodCall(call_id, result) {
- let iframe = document.getElementById("plugin_iframe").contentWindow;
- iframe.postMessage({'call_id': call_id, 'result': result}, "http://127.0.0.1:1337");
-}
-
-function installPlugin(request_id) {
- let id = `${new Date().getTime()}`;
- console.debug(JSON.stringify({
- "id": id,
- "method": "confirm_plugin_install",
- "args": {"request_id": request_id}
- }));
- document.getElementById('plugin_install_list').removeChild(document.getElementById(`plugin_install_prompt_${request_id}`));
-}
-
-function addPluginInstallPrompt(artifact, version, request_id) {
- let text = `
- <link rel="stylesheet" href="/static/styles.css">
-
- <div id="plugin_install_prompt_${request_id}" style="background-color: #0c131b; display: block; border: 1px solid #22262f; box-shadow: 0px 0px 8px #202020; width: calc(100% - 50px); padding: 0px 10px 10px 10px;">
- <h3>Install Plugin?</h3>
- <p style="font-size: 12px;">
- ${artifact}
- Version: ${version}
- </p>
- <button type="button" tabindex="0" class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable"
- onclick="installPlugin('${request_id}')">
- Install
- </button>
- <p style="margin: 2px;"></p>
- <button type="button" tabindex="0" class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable"
- onclick="document.getElementById('plugin_install_list').removeChild(document.getElementById('plugin_install_prompt_${request_id}'))">
- Cancel
- </button>
- </div>
- `;
- document.getElementById('plugin_install_list').innerHTML = text;
-
- execute_in_tab('SP', false, 'FocusNavController.DispatchVirtualButtonClick(28)')
-}
-
-(function () {
- const PLUGIN_ICON = `
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plugin" viewBox="0 0 16 16">
- <path fill-rule="evenodd" d="M1 8a7 7 0 1 1 2.898 5.673c-.167-.121-.216-.406-.002-.62l1.8-1.8a3.5 3.5 0 0 0
- 4.572-.328l1.414-1.415a.5.5 0 0 0 0-.707l-.707-.707 1.559-1.563a.5.5 0 1 0-.708-.706l-1.559 1.562-1.414-1.414
- 1.56-1.562a.5.5 0 1 0-.707-.706l-1.56 1.56-.707-.706a.5.5 0 0 0-.707 0L5.318 5.975a3.5 3.5 0 0 0-.328
- 4.571l-1.8 1.8c-.58.58-.62 1.6.121 2.137A8 8 0 1 0 0 8a.5.5 0 0 0 1 0Z"/>
- </svg>
- `;
-
- function createTitle(text) {
- return `<div id="plugin_title" class="quickaccessmenu_Title_34nl5">${text}</div>`;
- }
-
- function createPluginList() {
- let pages = document.getElementsByClassName("quickaccessmenu_AllTabContents_2yKG4 quickaccessmenu_Down_3rR0o")[0];
- let pluginPage = pages.children[pages.children.length - 1];
- pluginPage.innerHTML = createTitle("Plugins");
-
- pluginPage.innerHTML += `<div id="plugin_install_list" style="position: fixed; height: 100%; z-index: 99; transform: translate(5%, 0);"></div>`
-
- pluginPage.innerHTML += `<iframe id="plugin_iframe" style="border: none; width: 100%; height: 100%;" src="http://127.0.0.1:1337/plugins/iframe"></iframe>`;
- }
-
- function inject() {
- let tabs = document.getElementsByClassName("quickaccessmenu_TabContentColumn_2z5NL Panel Focusable")[0];
- tabs.children[tabs.children.length - 1].innerHTML = PLUGIN_ICON;
-
- createPluginList();
- }
-
- let injector = setInterval(function () {
- if (document.hasFocus()) {
- inject();
- document.getElementById("plugin_title").onclick = function() {
- reloadIframe();
- document.getElementById("plugin_title").innerText = "Plugins";
- }
- window.onmessage = function(ev) {
- let title = ev.data;
- if (title.startsWith("PLUGIN_LOADER__")) {
- document.getElementById("plugin_title").innerHTML = `
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-square-fill" viewBox="0 0 16 16">
- <path d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12zm-4.5-6.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H11.5a.5.5 0 0 0 0-1z"/>
- </svg>
- ${title.replace("PLUGIN_LOADER__", "")}
- `;
- }
- }
- clearInterval(injector);
- }
- }, 100);
-})(); \ No newline at end of file
diff --git a/plugin_loader/static/styles.css b/plugin_loader/static/styles.css
deleted file mode 100644
index 8d27a538..00000000
--- a/plugin_loader/static/styles.css
+++ /dev/null
@@ -1,3 +0,0 @@
-@import url("/steam_resource/css/2.css");
-@import url("/steam_resource/css/39.css");
-@import url("/steam_resource/css/library.css");
diff --git a/plugin_loader/templates/plugin_view.html b/plugin_loader/templates/plugin_view.html
deleted file mode 100644
index 9d7ba1bc..00000000
--- a/plugin_loader/templates/plugin_view.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<link rel="stylesheet" href="/static/styles.css">
-<script>
- const tile_iframes = [];
- window.addEventListener("message", function (evt) {
- tile_iframes.forEach(iframe => {
- iframe.contentWindow.postMessage(evt.data, "http://127.0.0.1:1337");
- });
- }, false);
-
- function loadPlugin(callsign, name) {
- this.parent.postMessage("PLUGIN_LOADER__"+name, "https://steamloopback.host");
- location.href = `/plugins/load_main/${callsign}`;
- }
-</script>
-
-{% if not plugins|length %}
- <div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
- <div class="quickaccesscontrols_EmptyNotifications_3ZjbM" style="padding-top:7px;">
- No plugins installed
- </div>
- </div>
-{% endif %}
-
-<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
- {% for plugin in plugins %}
- {% if plugin.tile_view_html|length %}
- <div class="quickaccesscontrols_PanelSectionRow_26R5w">
- <div onclick="loadPlugin('{{ plugin.callsign }}', '{{ plugin.name }}')"
- class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable"
- style="--indent-level:0; margin: 0px; padding: 0px; padding-top: 8px;">
- <iframe id="tile_view_iframe_{{ plugin.callsign }}"
- scrolling="no" marginwidth="0" marginheight="0"
- hspace="0" vspace="0" frameborder="0"
- style="border-radius: 2px;"
- src="/plugins/load_tile/{{ plugin.callsign }}">
- </iframe>
- <script>
- (function() {
- let iframe = document.getElementById("tile_view_iframe_{{ plugin.callsign }}");
- tile_iframes.push(document.getElementById("tile_view_iframe_{{ plugin.callsign }}"));
-
- iframe.onload = function() {
- let html = iframe.contentWindow.document.children[0];
- let last_height = 0;
-
- setInterval(function() {
- let height = iframe.contentWindow.document.children[0].scrollHeight;
- if (height != last_height) {
- iframe.height = height + "px";
- last_height = height;
- }
- }, 100);
-
- iframe.contentWindow.document.body.onclick = function () {
- loadPlugin('{{ plugin.callsign }}', '{{ plugin.name }}');
- };
- }
- })();
- </script>
- </div>
- </div>
- {% else %}
- <div class="quickaccesscontrols_PanelSectionRow_26R5w">
- <div onclick="loadPlugin('{{ plugin.callsign }}', '{{ plugin.name }}')"
- class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable"
- style="--indent-level:0; margin: 0px; padding: 0px; padding-top: 8px;">
- <div class="basicdialog_FieldChildren_279n8">
- <button type="button" tabindex="0"
- class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable">{{ plugin.name }}
- </button>
- </div>
- </div>
- </div>
- {% endif %}
- {% endfor %}
-</div>
diff --git a/requirements.txt b/requirements.txt
index c77a53ed..15fdb135 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
aiohttp==3.8.1
aiohttp-jinja2==1.5.0
-watchdog==2.1.7 \ No newline at end of file
+aiohttp_cors==0.7.0
+watchdog==2.1.7