diff --git a/asm/CMakeLists.txt b/asm/CMakeLists.txt index 0e25ff90..f82daa65 100644 --- a/asm/CMakeLists.txt +++ b/asm/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_C_COMPILER "emcc") set(CMAKE_C_LINK_EXECUTABLE "emcc") set(CMAKE_CXX_FLAGS "-O3 --llvm-lto 1 --memory-init-file 0 -s WASM=1 -s ASSERTIONS=1") # -s ALLOW_MEMORY_GROWTH=1 -O3 set(CMAKE_VERBOSE_MAKEFILE ON) -set(CMAKE_EXE_LINKER_FLAGS "-s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\", \"Pointer_stringify\"]' -s ENVIRONMENT='worker'") # +set(CMAKE_EXE_LINKER_FLAGS "-s EXPORTED_FUNCTIONS='[\"_malloc\", \"_free\"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]' -s ENVIRONMENT='worker' --pre-js ${CMAKE_SOURCE_DIR}/init.js") # #add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/generated/") diff --git a/asm/init.js b/asm/init.js new file mode 100644 index 00000000..48cb420e --- /dev/null +++ b/asm/init.js @@ -0,0 +1,2 @@ +for(const callback of Array.isArray(self.__init_em_module) ? self.__init_em_module : []) + callback(Module); \ No newline at end of file diff --git a/asm/src/opus.cpp b/asm/src/opus.cpp index f10d40de..854ad3e7 100644 --- a/asm/src/opus.cpp +++ b/asm/src/opus.cpp @@ -23,6 +23,7 @@ extern "C" { "Memory allocation has failed" //-7 (OPUS_ALLOC_FAIL) }; + EMSCRIPTEN_KEEPALIVE inline const char* opus_error_message(int error) { error = abs(error); if(error > 0 && error <= 7) return opus_errors[error - 1]; @@ -59,11 +60,13 @@ extern "C" { INVOKE_OPUS(error, opus_encoder_ctl, handle->encoder, OPUS_SET_COMPLEXITY(1)); //INVOKE_OPUS(error, opus_encoder_ctl, handle->encoder, OPUS_SET_BITRATE(4740)); + /* //This method is obsolete! EM_ASM( printMessageToServerTab('Encoder initialized!'); printMessageToServerTab(' Comprexity: 1'); printMessageToServerTab(' Bitrate: 4740'); ); + */ return true; } @@ -98,9 +101,11 @@ extern "C" { auto result = opus_encode_float(handle->encoder, (float*) buffer, length / handle->channelCount, buffer, maxLength); if(result < 0) return result; auto end = currentMillies(); + /* //This message is obsolete EM_ASM({ - printMessageToServerTab("codec_opus_encode(...) tooks " + $0 + "ms to execute!"); - }, end - begin); + printMessageToServerTab("codec_opus_encode(...) tooks " + $0 + "ms to execute!"); + }, end - begin); + */ return result; } diff --git a/file.ts b/file.ts index 249d9c91..4086a735 100644 --- a/file.ts +++ b/file.ts @@ -463,7 +463,7 @@ const WEB_APP_FILE_LIST = [ "path": "./", "local-path": "./shared/html/" }, - { /* javascript loader for releases */ + { /* javascript files as manifest.json */ "type": "js", "search-pattern": /.*$/, "build-target": "dev|rel", @@ -471,7 +471,14 @@ const WEB_APP_FILE_LIST = [ "path": "js/", "local-path": "./dist/" }, + { /* loader javascript file */ + "type": "js", + "search-pattern": /.*$/, + "build-target": "dev|rel", + "path": "js/", + "local-path": "./loader/dist/" + }, { /* shared javascript files (WebRTC adapter) */ "type": "js", "search-pattern": /.*\.js$/, @@ -661,41 +668,49 @@ namespace generator { return result.digest("hex"); } + const rreaddir = async p => { + const result = []; + try { + const files = await fs.readdir(p); + for(const file of files) { + const file_path = path.join(p, file); + + const info = await fs.stat(file_path); + if(info.isDirectory()) { + result.push(...await rreaddir(file_path)); + } else { + result.push(file_path); + } + } + } catch(error) { + if(error.code === "ENOENT") + return []; + throw error; + } + return result; + }; + + function file_matches_options(file: ProjectResource, options: SearchOptions) { + if(typeof file["web-only"] === "boolean" && file["web-only"] && options.target !== "web") + return false; + + if(typeof file["client-only"] === "boolean" && file["client-only"] && options.target !== "client") + return false; + + if(typeof file["serve-only"] === "boolean" && file["serve-only"] && !options.serving) + return false; + + if(!file["build-target"].split("|").find(e => e === options.mode)) + return false; + + return !(Array.isArray(file["req-parm"]) && file["req-parm"].find(e => !options.parameter.find(p => p.toLowerCase() === e.toLowerCase()))); + } + export async function search_files(files: ProjectResource[], options: SearchOptions) : Promise { const result: Entry[] = []; - const rreaddir = async p => { - const result = []; - try { - const files = await fs.readdir(p); - for(const file of files) { - const file_path = path.join(p, file); - - const info = await fs.stat(file_path); - if(info.isDirectory()) { - result.push(...await rreaddir(file_path)); - } else { - result.push(file_path); - } - } - } catch(error) { - if(error.code === "ENOENT") - return []; - throw error; - } - return result; - }; - for(const file of files) { - if(typeof file["web-only"] === "boolean" && file["web-only"] && options.target !== "web") - continue; - if(typeof file["client-only"] === "boolean" && file["client-only"] && options.target !== "client") - continue; - if(typeof file["serve-only"] === "boolean" && file["serve-only"] && !options.serving) - continue; - if(!file["build-target"].split("|").find(e => e === options.mode)) - continue; - if(Array.isArray(file["req-parm"]) && file["req-parm"].find(e => !options.parameter.find(p => p.toLowerCase() === e.toLowerCase()))) + if(!file_matches_options(file, options)) continue; const normal_local = path.normalize(path.join(options.source_path, file["local-path"])); @@ -723,23 +738,52 @@ namespace generator { return result; } + + export async function search_http_file(files: ProjectResource[], target_file: string, options: SearchOptions) : Promise { + for(const file of files) { + if(!file_matches_options(file, options)) + continue; + + if(file.path !== "./" && !target_file.startsWith("/" + file.path.replace(/\\/g, "/"))) + continue; + + const normal_local = path.normalize(path.join(options.source_path, file["local-path"])); + const files: string[] = await rreaddir(normal_local); + for(const f of files) { + const local_name = f.substr(normal_local.length); + if(!local_name.match(file["search-pattern"]) && !local_name.replace("\\\\", "/").match(file["search-pattern"])) + continue; + + if(typeof(file["search-exclude"]) !== "undefined" && f.match(file["search-exclude"])) + continue; + + if("/" + path.join(file.path, local_name).replace(/\\/g, "/") === target_file) + return f; + } + } + + return undefined; + } } namespace server { + import SearchOptions = generator.SearchOptions; export type Options = { port: number; php: string; + + search_options: SearchOptions; } const exec: (command: string) => Promise<{ stdout: string, stderr: string }> = util.promisify(cp.exec); - let files: (generator.Entry & { http_path: string; })[] = []; + let files: ProjectResource[] = []; let server: http.Server; let php: string; - export async function launch(_files: generator.Entry[], options: Options) { - //Don't use this check anymore, because we're searching within the PATH variable - //if(!await fs.exists(options.php) || !(await fs.stat(options.php)).isFile()) - // throw "invalid php interpreter (not found)"; + let options: Options; + export async function launch(_files: ProjectResource[], options_: Options) { + options = options_; + files = _files; try { const info = await exec(options.php + " --version"); @@ -763,17 +807,6 @@ namespace server { resolve(); }); }); - - files = _files.map(e =>{ - return { - type: e.type, - name: e.name, - hash: e.hash, - local_path: e.local_path, - target_path: e.target_path, - http_path: "/" + e.target_path.replace(/\\/g, "/") - } - }); } export async function shutdown() { @@ -817,8 +850,8 @@ namespace server { }); } - function serve_file(pathname: string, query: any, response: http.ServerResponse) { - const file = files.find(e => e.http_path === pathname); + async function serve_file(pathname: string, query: any, response: http.ServerResponse) { + const file = await generator.search_http_file(files, pathname, options.search_options); if(!file) { console.log("[SERVER] Client requested unknown file %s", pathname); response.writeHead(404); @@ -827,13 +860,13 @@ namespace server { return; } - let type = mt.lookup(path.extname(file.local_path)) || "text/html"; - console.log("[SERVER] Serving file %s (%s) (%s)", file.target_path, type, file.local_path); - if(path.extname(file.local_path) === ".php") { - serve_php(file.local_path, query, response); + let type = mt.lookup(path.extname(file)) || "text/html"; + console.log("[SERVER] Serving file %s", file, type); + if(path.extname(file) === ".php") { + serve_php(file, query, response); return; } - const fis = fs.createReadStream(file.local_path); + const fis = fs.createReadStream(file); response.writeHead(200, "success", { "Content-Type": type + "; charset=utf-8" @@ -846,23 +879,22 @@ namespace server { fis.on("data", data => response.write(data)); } - function handle_api_request(request: http.IncomingMessage, response: http.ServerResponse, url: url_utils.UrlWithParsedQuery) { + async function handle_api_request(request: http.IncomingMessage, response: http.ServerResponse, url: url_utils.UrlWithParsedQuery) { if(url.query["type"] === "files") { response.writeHead(200, { "info-version": 1 }); response.write("type\thash\tpath\tname\n"); - for(const file of files) - if(file.http_path.endsWith(".php")) - response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.http_path) + "\t" + path.basename(file.http_path, ".php") + ".html" + "\n"); + for(const file of await generator.search_files(files, options.search_options)) + if(file.name.endsWith(".php")) + response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.target_path) + "\t" + path.basename(file.name, ".php") + ".html" + "\n"); else - response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.http_path) + "\t" + path.basename(file.http_path) + "\n"); + response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.target_path) + "\t" + file.name + "\n"); response.end(); return; } else if(url.query["type"] === "file") { let p = path.join(url.query["path"] as string, url.query["name"] as string).replace(/\\/g, "/"); if(p.endsWith(".html")) { - const np = p.substr(0, p.length - 5) + ".php"; - if(files.find(e => e.http_path == np) && !files.find(e => e.http_path == p)) - p = np; + const np = await generator.search_http_file(files, p, options.search_options); + if(np) p = np; } serve_file(p, url.query, response); return; @@ -1041,17 +1073,16 @@ function php_exe() : string { } async function main_serve(target: "client" | "web", mode: "rel" | "dev", port: number) { - const files = await generator.search_files(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, { - source_path: __dirname, - parameter: [], - target: target, - mode: mode, - serving: true - }); - - await server.launch(files, { + await server.launch(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, { port: port, php: php_exe(), + search_options: { + source_path: __dirname, + parameter: [], + target: target, + mode: mode, + serving: true + } }); console.log("Server started on %d", port); @@ -1060,14 +1091,6 @@ async function main_serve(target: "client" | "web", mode: "rel" | "dev", port: n } async function main_develop(node: boolean, target: "client" | "web", port: number, flags: string[]) { - const files = await generator.search_files(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, { - source_path: __dirname, - parameter: [], - target: target, - mode: "dev", - serving: true - }); - const tscwatcher = new watcher.TSCWatcher(); try { if(flags.indexOf("--no-tsc") == -1) @@ -1079,9 +1102,16 @@ async function main_develop(node: boolean, target: "client" | "web", port: numbe await sasswatcher.start(); try { - await server.launch(files, { + await server.launch(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, { port: port, php: php_exe(), + search_options: { + source_path: __dirname, + parameter: [], + target: target, + mode: "dev", + serving: true + } }); } catch(error) { console.error("Failed to start server: %o", error instanceof Error ? error.message : error); diff --git a/loader/app/app.ts b/loader/app/app.ts index b1c5d540..4847410a 100644 --- a/loader/app/app.ts +++ b/loader/app/app.ts @@ -1,4 +1,4 @@ -import * as loader from "./loader"; +import * as loader from "./loader/loader"; declare global { interface Window { @@ -8,6 +8,11 @@ declare global { const node_require: typeof require = window.require; +function cache_tag() { + const ui = ui_version(); + return "?_ts=" + (!!ui && ui !== "unknown" ? ui : Date.now()); +} + let _ui_version; export function ui_version() { if(typeof(_ui_version) !== "string") { @@ -22,6 +27,15 @@ export function ui_version() { return _ui_version; } +interface Manifest { + version: number; + + chunks: {[key: string]: { + hash: string, + file: string + }[]}; +} + /* all javascript loaders */ const loader_javascript = { detect_type: async () => { @@ -72,7 +86,7 @@ const loader_javascript = { }, load_scripts: async () => { if(!window.require) { - await loader.load_script(["vendor/jquery/jquery.min.js"]); + await loader.scripts.load(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() }); } else { /* loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { @@ -84,45 +98,41 @@ const loader_javascript = { }); */ } - await loader.load_script(["vendor/DOMPurify/purify.min.js"]); + await loader.scripts.load(["vendor/DOMPurify/purify.min.js"], { cache_tag: cache_tag() }); - await loader.load_script("vendor/jsrender/jsrender.min.js"); - await loader.load_scripts([ + await loader.scripts.load("vendor/jsrender/jsrender.min.js", { cache_tag: cache_tag() }); + await loader.scripts.load_multiple([ ["vendor/xbbcode/src/parser.js"], ["vendor/moment/moment.js"], ["vendor/twemoji/twemoji.min.js", ""], /* empty string means not required */ ["vendor/highlight/highlight.pack.js", ""], /* empty string means not required */ ["vendor/remarkable/remarkable.min.js", ""], /* empty string means not required */ ["adapter/adapter-latest.js", "https://webrtc.github.io/adapter/adapter-latest.js"] - ]); - await loader.load_scripts([ - ["vendor/emoji-picker/src/jquery.lsxemojipicker.js"] - ]); + ], { + cache_tag: cache_tag(), + max_parallel_requests: -1 + }); - if(!loader.version().debug_mode) { - loader.register_task(loader.Stage.JAVASCRIPT, { - name: "scripts release", - priority: 20, - function: loader_javascript.load_release - }); - } else { - loader.register_task(loader.Stage.JAVASCRIPT, { - name: "scripts debug", - priority: 20, - function: loader_javascript.load_scripts_debug - }); + await loader.scripts.load("vendor/emoji-picker/src/jquery.lsxemojipicker.js", { cache_tag: cache_tag() }); + + let manifest: Manifest; + try { + const response = await fetch("js/manifest.json"); + if(!response.ok) throw response.status + " " + response.statusText; + + manifest = await response.json(); + } catch(error) { + console.error("Failed to load javascript manifest: %o", error); + loader.critical_error("Failed to load manifest.json", error); + throw "failed to load manifest.json"; } - }, - load_scripts_debug: async () => { - await loader.load_scripts(["js/shared-app.js"]) - }, - load_release: async () => { - console.log("Load for release!"); + if(manifest.version !== 1) + throw "invalid manifest version"; - await loader.load_scripts([ - //Load general API's - ["js/client.min.js", "js/client.js"] - ]); + await loader.scripts.load_multiple(manifest.chunks["shared-app"].map(e => "js/" + e.file), { + cache_tag: undefined, + max_parallel_requests: -1 + }); } }; @@ -149,15 +159,20 @@ const loader_webassembly = { const loader_style = { load_style: async () => { - await loader.load_styles([ + const options = { + cache_tag: cache_tag(), + max_parallel_requests: -1 + }; + + await loader.style.load_multiple([ "vendor/xbbcode/src/xbbcode.css" - ]); - await loader.load_styles([ + ], options); + await loader.style.load_multiple([ "vendor/emoji-picker/src/jquery.lsxemojipicker.css" - ]); - await loader.load_styles([ + ], options); + await loader.style.load_multiple([ ["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */ - ]); + ], options); if(loader.version().debug_mode) { await loader_style.load_style_debug(); @@ -167,7 +182,7 @@ const loader_style = { }, load_style_debug: async () => { - await loader.load_styles([ + await loader.style.load_multiple([ "css/static/main.css", "css/static/main-layout.css", "css/static/helptag.css", @@ -218,14 +233,20 @@ const loader_style = { "css/static/htmltags.css", "css/static/hostbanner.css", "css/static/menu-bar.css" - ]); + ], { + cache_tag: cache_tag(), + max_parallel_requests: -1 + }); }, load_style_release: async () => { - await loader.load_styles([ + await loader.style.load_multiple([ "css/static/base.css", "css/static/main.css", - ]); + ], { + cache_tag: cache_tag(), + max_parallel_requests: -1 + }); } }; @@ -313,11 +334,14 @@ loader.register_task(loader.Stage.STYLE, { loader.register_task(loader.Stage.TEMPLATES, { name: "templates", function: async () => { - await loader.load_templates([ + await loader.templates.load_multiple([ "templates.html", "templates/modal/musicmanage.html", "templates/modal/newcomer.html", - ]); + ], { + cache_tag: cache_tag(), + max_parallel_requests: -1 + }); }, priority: 10 }); diff --git a/loader/app/certaccept.ts b/loader/app/certaccept.ts index 7c12bac7..f3da64e9 100644 --- a/loader/app/certaccept.ts +++ b/loader/app/certaccept.ts @@ -1,4 +1,4 @@ -import * as loader from "./loader"; +import * as loader from "./loader/loader"; let is_debug = false; diff --git a/loader/app/index.ts b/loader/app/index.ts index 536f7ee7..403f20b7 100644 --- a/loader/app/index.ts +++ b/loader/app/index.ts @@ -1,5 +1,5 @@ import * as loader from "./app"; -import * as loader_base from "./loader"; +import * as loader_base from "./loader/loader"; export = loader_base; loader.run(); \ No newline at end of file diff --git a/loader/app/loader.ts b/loader/app/loader/loader.ts similarity index 50% rename from loader/app/loader.ts rename to loader/app/loader/loader.ts index 2df47c4f..4faaebcc 100644 --- a/loader/app/loader.ts +++ b/loader/app/loader/loader.ts @@ -1,5 +1,8 @@ -import {AppVersion} from "../exports/loader"; -import {type} from "os"; +import {AppVersion} from "tc-loader"; +import {LoadSyntaxError, script_name} from "./utils"; +import * as script_loader from "./script_loader"; +import * as style_loader from "./style_loader"; +import * as template_loader from "./template_loader"; declare global { interface Window { @@ -205,10 +208,6 @@ export async function execute() { } } - /* cleanup */ - { - _script_promises = {}; - } if(config.verbose) console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin); } export function execute_managed() { @@ -233,347 +232,32 @@ export function execute_managed() { }); } -export type DependSource = { - url: string; - depends: string[]; -} -export type SourcePath = string | DependSource | string[]; - -function script_name(path: SourcePath, html: boolean) { - if(Array.isArray(path)) { - let buffer = ""; - let _or = " or "; - for(let entry of path) - buffer += _or + script_name(entry, html); - return buffer.slice(_or.length); - } else if(typeof(path) === "string") - return html ? "" + path + "" : path; - else - return html ? "" + path.url + "" : path.url; -} - -class SyntaxError { - source: any; - - constructor(source: any) { - this.source = source; - } -} - -let _script_promises: {[key: string]: Promise} = {}; -export async function load_script(path: SourcePath) : Promise { - if(Array.isArray(path)) { //We have some fallback - return load_script(path[0]).catch(error => { - console.log(typeof error + " - " + (error instanceof SyntaxError)); - if(error instanceof SyntaxError) - return Promise.reject(error); - - if(path.length > 1) - return load_script(path.slice(1)); - - return Promise.reject(error); - }); - } else { - const source = typeof(path) === "string" ? {url: path, depends: []} : path; - if(source.url.length == 0) return Promise.resolve(); - - return _script_promises[source.url] = (async () => { - /* await depends */ - for(const depend of source.depends) { - if(!_script_promises[depend]) - throw "Missing dependency " + depend; - await _script_promises[depend]; - } - - const tag: HTMLScriptElement = document.createElement("script"); - - await new Promise((resolve, reject) => { - let error = false; - const error_handler = (event: ErrorEvent) => { - if(event.filename == tag.src && event.message.indexOf("Illegal constructor") == -1) { //Our tag throw an uncaught error - if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error); - window.removeEventListener('error', error_handler as any); - - reject(new SyntaxError(event.error)); - event.preventDefault(); - error = true; - } - }; - window.addEventListener('error', error_handler as any); - - const cleanup = () => { - tag.onerror = undefined; - tag.onload = undefined; - - clearTimeout(timeout_handle); - window.removeEventListener('error', error_handler as any); - }; - const timeout_handle = setTimeout(() => { - cleanup(); - reject("timeout"); - }, 5000); - tag.type = "application/javascript"; - tag.async = true; - tag.defer = true; - tag.onerror = error => { - cleanup(); - tag.remove(); - reject(error); - }; - tag.onload = () => { - cleanup(); - - if(config.verbose) console.debug("Script %o loaded", path); - setTimeout(resolve, 100); - }; - - document.getElementById("scripts").appendChild(tag); - - tag.src = source.url + (cache_tag || ""); - }); - })(); - } -} - -export async function load_scripts(paths: SourcePath[]) : Promise { - const promises: Promise[] = []; - const errors: { - script: SourcePath, - error: any - }[] = []; - - for(const script of paths) - promises.push(load_script(script).catch(error => { - errors.push({ - script: script, - error: error - }); - return Promise.resolve(); - })); - - await Promise.all([...promises]); - - if(errors.length > 0) { - if(config.error) { - console.error("Failed to load the following scripts:"); - for(const script of errors) { - const sname = script_name(script.script, false); - if(script.error instanceof SyntaxError) { - const source = script.error.source as Error; - if(source.name === "TypeError") { - let prefix = ""; - while(prefix.length < sname.length + 7) prefix += " "; - console.log(" - %s: %s:\n%s", sname, source.message, source.stack.split("\n").map(e => prefix + e.trim()).slice(1).join("\n")); - } else { - console.log(" - %s: %o", sname, source); - } - } else { - console.log(" - %s: %o", sname, script.error); - } - } - } - - critical_error("Failed to load script " + script_name(errors[0].script, true) + "
" + "View the browser console for more information!"); - throw "failed to load script " + script_name(errors[0].script, false); - } -} - -export async function load_style(path: SourcePath) : Promise { - if(Array.isArray(path)) { //We have some fallback - return load_style(path[0]).catch(error => { - if(error instanceof SyntaxError) - return Promise.reject(error.source); - - if(path.length > 1) - return load_script(path.slice(1)); - - return Promise.reject(error); - }); - } else { - if(!path) { - return Promise.resolve(); - } - - return new Promise((resolve, reject) => { - const tag: HTMLLinkElement = document.createElement("link"); - - let error = false; - const error_handler = (event: ErrorEvent) => { - if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error); - if(event.filename == tag.href) { //FIXME! - window.removeEventListener('error', error_handler as any); - - reject(new SyntaxError(event.error)); - event.preventDefault(); - error = true; - } - }; - window.addEventListener('error', error_handler as any); - - tag.type = "text/css"; - tag.rel = "stylesheet"; - - const cleanup = () => { - tag.onerror = undefined; - tag.onload = undefined; - - clearTimeout(timeout_handle); - window.removeEventListener('error', error_handler as any); - }; - - const timeout_handle = setTimeout(() => { - cleanup(); - reject("timeout"); - }, 5000); - - tag.onerror = error => { - cleanup(); - tag.remove(); - if(config.error) - console.error("File load error for file %s: %o", path, error); - reject("failed to load file " + path); - }; - tag.onload = () => { - cleanup(); - { - const css: CSSStyleSheet = tag.sheet as CSSStyleSheet; - const rules = css.cssRules; - const rules_remove: number[] = []; - const rules_add: string[] = []; - - for(let index = 0; index < rules.length; index++) { - const rule = rules.item(index); - let rule_text = rule.cssText; - - if(rule.cssText.indexOf("%%base_path%%") != -1) { - rules_remove.push(index); - rules_add.push(rule_text.replace("%%base_path%%", document.location.origin + document.location.pathname)); - } - } - - for(const index of rules_remove.sort((a, b) => b > a ? 1 : 0)) { - if(css.removeRule) - css.removeRule(index); - else - css.deleteRule(index); - } - for(const rule of rules_add) - css.insertRule(rule, rules_remove[0]); - } - - if(config.verbose) console.debug("Style sheet %o loaded", path); - setTimeout(resolve, 100); - }; - - document.getElementById("style").appendChild(tag); - tag.href = path + (cache_tag || ""); - }); - } -} - -export async function load_styles(paths: SourcePath[]) : Promise { - const promises: Promise[] = []; - const errors: { - sheet: SourcePath, - error: any - }[] = []; - - for(const sheet of paths) - promises.push(load_style(sheet).catch(error => { - errors.push({ - sheet: sheet, - error: error - }); - return Promise.resolve(); - })); - - await Promise.all([...promises]); - - if(errors.length > 0) { - if(config.error) { - console.error("Failed to load the following style sheet:"); - for(const sheet of errors) - console.log(" - %o: %o", sheet.sheet, sheet.error); - } - - critical_error("Failed to load style sheet " + script_name(errors[0].sheet, true) + "
" + "View the browser console for more information!"); - throw "failed to load style sheet " + script_name(errors[0].sheet, false); - } -} - -export async function load_template(path: SourcePath) : Promise { - try { - const response = await $.ajax(path + (cache_tag || "")); - - let node = document.createElement("html"); - node.innerHTML = response; - let tags: HTMLCollection; - if(node.getElementsByTagName("body").length > 0) - tags = node.getElementsByTagName("body")[0].children; - else - tags = node.children; - - let root = document.getElementById("templates"); - if(!root) { - critical_error("Failed to find template tag!"); - throw "Failed to find template tag"; - } - while(tags.length > 0){ - let tag = tags.item(0); - root.appendChild(tag); - - } - } catch(error) { - let msg; - if('responseText' in error) - msg = error.responseText; - else if(error instanceof Error) - msg = error.message; - - critical_error("failed to load template " + script_name(path, true), msg); - throw "template error"; - } -} - -export async function load_templates(paths: SourcePath[]) : Promise { - const promises: Promise[] = []; - const errors: { - template: SourcePath, - error: any - }[] = []; - - for(const template of paths) - promises.push(load_template(template).catch(error => { - errors.push({ - template: template, - error: error - }); - return Promise.resolve(); - })); - - await Promise.all([...promises]); - - if(errors.length > 0) { - if (config.error) { - console.error("Failed to load the following templates:"); - for (const sheet of errors) - console.log(" - %s: %o", script_name(sheet.template, false), sheet.error); - } - - critical_error("Failed to load template " + script_name(errors[0].template, true) + "
" + "View the browser console for more information!"); - throw "failed to load template " + script_name(errors[0].template, false); +let _fadeout_warned; +export function hide_overlay() { + if(typeof($) === "undefined") { + if(!_fadeout_warned) + console.warn("Could not fadeout loader screen. Missing jquery functions."); + _fadeout_warned = true; + return; } + const animation_duration = 750; + + $(".loader .bookshelf_wrapper").animate({top: 0, opacity: 0}, animation_duration); + $(".loader .half").animate({width: 0}, animation_duration, () => { + $(".loader").detach(); + }); } +/* versions management */ let version_: AppVersion; export function version() : AppVersion { return version_; } export function set_version(version: AppVersion) { version_ = version; } -export type ErrorHandler = (message: string, detail: string) => void; +/* critical error handler */ +export type ErrorHandler = (message: string, detail: string) => void; let _callback_critical_error: ErrorHandler; let _callback_critical_called: boolean = false; - export function critical_error(message: string, detail?: string) { if(_callback_critical_called) { console.warn("[CRITICAL] %s", message); @@ -603,29 +287,24 @@ export function critical_error(message: string, detail?: string) { tag.classList.add("shown"); } - export function critical_error_handler(handler?: ErrorHandler, override?: boolean) : ErrorHandler { if((typeof(handler) === "object" && handler !== _callback_critical_error) || override) _callback_critical_error = handler; return _callback_critical_error; } -let _fadeout_warned; -export function hide_overlay() { - if(typeof($) === "undefined") { - if(!_fadeout_warned) - console.warn("Could not fadeout loader screen. Missing jquery functions."); - _fadeout_warned = true; - return; - } - const animation_duration = 750; - - $(".loader .bookshelf_wrapper").animate({top: 0, opacity: 0}, animation_duration); - $(".loader .half").animate({width: 0}, animation_duration, () => { - $(".loader").detach(); - }); +/* loaders */ +export type DependSource = { + url: string; + depends: string[]; } +export type SourcePath = string | DependSource | string[]; +export const scripts = script_loader; +export const style = style_loader; +export const templates = template_loader; + +/* Hello World message */ { const hello_world = () => { diff --git a/loader/app/loader/script_loader.ts b/loader/app/loader/script_loader.ts new file mode 100644 index 00000000..7c47ae26 --- /dev/null +++ b/loader/app/loader/script_loader.ts @@ -0,0 +1,121 @@ +import {config, critical_error, SourcePath} from "./loader"; +import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils"; + +let _script_promises: {[key: string]: Promise} = {}; + +function load_script_url(url: string) : Promise { + if(typeof _script_promises[url] === "object") + return _script_promises[url]; + + return (_script_promises[url] = new Promise((resolve, reject) => { + const script_tag: HTMLScriptElement = document.createElement("script"); + + let error = false; + const error_handler = (event: ErrorEvent) => { + if(event.filename == script_tag.src && event.message.indexOf("Illegal constructor") == -1) { //Our tag throw an uncaught error + if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error); + window.removeEventListener('error', error_handler as any); + + reject(new LoadSyntaxError(event.error)); + event.preventDefault(); + error = true; + } + }; + window.addEventListener('error', error_handler as any); + + const cleanup = () => { + script_tag.onerror = undefined; + script_tag.onload = undefined; + + clearTimeout(timeout_handle); + window.removeEventListener('error', error_handler as any); + }; + const timeout_handle = setTimeout(() => { + cleanup(); + reject("timeout"); + }, 5000); + script_tag.type = "application/javascript"; + script_tag.async = true; + script_tag.defer = true; + script_tag.onerror = error => { + cleanup(); + script_tag.remove(); + reject(error); + }; + script_tag.onload = () => { + cleanup(); + + if(config.verbose) console.debug("Script %o loaded", url); + setTimeout(resolve, 100); + }; + + document.getElementById("scripts").appendChild(script_tag); + + script_tag.src = url; + })).then(result => { + /* cleanup memory */ + _script_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */ + return _script_promises[url]; + }).catch(error => { + /* cleanup memory */ + _script_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */ + return _script_promises[url]; + }); +} + +export interface Options { + cache_tag?: string; +} + +export async function load(path: SourcePath, options: Options) : Promise { + if(Array.isArray(path)) { //We have fallback scripts + return load(path[0], options).catch(error => { + if(error instanceof LoadSyntaxError) + return Promise.reject(error); + + if(path.length > 1) + return load(path.slice(1), options); + + return Promise.reject(error); + }); + } else { + const source = typeof(path) === "string" ? {url: path, depends: []} : path; + if(source.url.length == 0) return Promise.resolve(); + + /* await depends */ + for(const depend of source.depends) { + if(!_script_promises[depend]) + throw "Missing dependency " + depend; + await _script_promises[depend]; + } + await load_script_url(source.url + (options.cache_tag || "")); + } +} + +type MultipleOptions = Options | ParallelOptions; +export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise { + const result = await load_parallel(paths, e => load(e, options), e => script_name(e, false), options); + if(result.failed.length > 0) { + if(config.error) { + console.error("Failed to load the following scripts:"); + for(const script of result.failed) { + const sname = script_name(script.request, false); + if(script.error instanceof LoadSyntaxError) { + const source = script.error.source as Error; + if(source.name === "TypeError") { + let prefix = ""; + while(prefix.length < sname.length + 7) prefix += " "; + console.log(" - %s: %s:\n%s", sname, source.message, source.stack.split("\n").map(e => prefix + e.trim()).slice(1).join("\n")); + } else { + console.log(" - %s: %o", sname, source); + } + } else { + console.log(" - %s: %o", sname, script.error); + } + } + } + + critical_error("Failed to load script " + script_name(result.failed[0].request, true) + "
" + "View the browser console for more information!"); + throw "failed to load script " + script_name(result.failed[0].request, false); + } +} \ No newline at end of file diff --git a/loader/app/loader/style_loader.ts b/loader/app/loader/style_loader.ts new file mode 100644 index 00000000..7eb84153 --- /dev/null +++ b/loader/app/loader/style_loader.ts @@ -0,0 +1,142 @@ +import {config, critical_error, SourcePath} from "./loader"; +import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils"; + +let _style_promises: {[key: string]: Promise} = {}; + +function load_style_url(url: string) : Promise { + if(typeof _style_promises[url] === "object") + return _style_promises[url]; + + return (_style_promises[url] = new Promise((resolve, reject) => { + const tag: HTMLLinkElement = document.createElement("link"); + + let error = false; + const error_handler = (event: ErrorEvent) => { + if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error); + if(event.filename == tag.href) { //FIXME! + window.removeEventListener('error', error_handler as any); + + reject(new SyntaxError(event.error)); + event.preventDefault(); + error = true; + } + }; + window.addEventListener('error', error_handler as any); + + tag.type = "text/css"; + tag.rel = "stylesheet"; + + const cleanup = () => { + tag.onerror = undefined; + tag.onload = undefined; + + clearTimeout(timeout_handle); + window.removeEventListener('error', error_handler as any); + }; + + const timeout_handle = setTimeout(() => { + cleanup(); + reject("timeout"); + }, 5000); + + tag.onerror = error => { + cleanup(); + tag.remove(); + if(config.error) + console.error("File load error for file %s: %o", url, error); + reject("failed to load file " + url); + }; + tag.onload = () => { + cleanup(); + { + const css: CSSStyleSheet = tag.sheet as CSSStyleSheet; + const rules = css.cssRules; + const rules_remove: number[] = []; + const rules_add: string[] = []; + + for(let index = 0; index < rules.length; index++) { + const rule = rules.item(index); + let rule_text = rule.cssText; + + if(rule.cssText.indexOf("%%base_path%%") != -1) { + rules_remove.push(index); + rules_add.push(rule_text.replace("%%base_path%%", document.location.origin + document.location.pathname)); + } + } + + for(const index of rules_remove.sort((a, b) => b > a ? 1 : 0)) { + if(css.removeRule) + css.removeRule(index); + else + css.deleteRule(index); + } + for(const rule of rules_add) + css.insertRule(rule, rules_remove[0]); + } + + if(config.verbose) console.debug("Style sheet %o loaded", url); + setTimeout(resolve, 100); + }; + + document.getElementById("style").appendChild(tag); + tag.href = url; + })).then(result => { + /* cleanup memory */ + _style_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */ + return _style_promises[url]; + }).catch(error => { + /* cleanup memory */ + _style_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */ + return _style_promises[url]; + }); +} + +export interface Options { + cache_tag?: string; +} + +export async function load(path: SourcePath, options: Options) : Promise { + if(Array.isArray(path)) { //We have fallback scripts + return load(path[0], options).catch(error => { + if(error instanceof LoadSyntaxError) + return Promise.reject(error); + + if(path.length > 1) + return load(path.slice(1), options); + + return Promise.reject(error); + }); + } else { + const source = typeof(path) === "string" ? {url: path, depends: []} : path; + if(source.url.length == 0) return Promise.resolve(); + + /* await depends */ + for(const depend of source.depends) { + if(!_style_promises[depend]) + throw "Missing dependency " + depend; + await _style_promises[depend]; + } + await load_style_url(source.url + (options.cache_tag || "")); + } +} + +export type MultipleOptions = Options | ParallelOptions; +export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise { + const result = await load_parallel(paths, e => load(e, options), e => script_name(e, false), options); + if(result.failed.length > 0) { + if(config.error) { + console.error("Failed to load the following style sheets:"); + for(const style of result.failed) { + const sname = script_name(style.request, false); + if(style.error instanceof LoadSyntaxError) { + console.log(" - %s: %o", sname, style.error.source); + } else { + console.log(" - %s: %o", sname, style.error); + } + } + } + + critical_error("Failed to load style " + script_name(result.failed[0].request, true) + "
" + "View the browser console for more information!"); + throw "failed to load style " + script_name(result.failed[0].request, false); + } +} \ No newline at end of file diff --git a/loader/app/loader/template_loader.ts b/loader/app/loader/template_loader.ts new file mode 100644 index 00000000..7d503af4 --- /dev/null +++ b/loader/app/loader/template_loader.ts @@ -0,0 +1,90 @@ +import {config, critical_error, SourcePath} from "./loader"; +import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils"; + +let _template_promises: {[key: string]: Promise} = {}; + +function load_template_url(url: string) : Promise { + if(typeof _template_promises[url] === "object") + return _template_promises[url]; + + return (_template_promises[url] = (async () => { + const response = await $.ajax(url); + + let node = document.createElement("html"); + node.innerHTML = response; + let tags: HTMLCollection; + if(node.getElementsByTagName("body").length > 0) + tags = node.getElementsByTagName("body")[0].children; + else + tags = node.children; + + let root = document.getElementById("templates"); + if(!root) { + critical_error("Failed to find template tag!"); + throw "Failed to find template tag"; + } + while(tags.length > 0){ + let tag = tags.item(0); + root.appendChild(tag); + + } + })()).then(result => { + /* cleanup memory */ + _template_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */ + return _template_promises[url]; + }).catch(error => { + /* cleanup memory */ + _template_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */ + return _template_promises[url]; + }); +} + +export interface Options { + cache_tag?: string; +} + +export async function load(path: SourcePath, options: Options) : Promise { + if(Array.isArray(path)) { //We have fallback scripts + return load(path[0], options).catch(error => { + if(error instanceof LoadSyntaxError) + return Promise.reject(error); + + if(path.length > 1) + return load(path.slice(1), options); + + return Promise.reject(error); + }); + } else { + const source = typeof(path) === "string" ? {url: path, depends: []} : path; + if(source.url.length == 0) return Promise.resolve(); + + /* await depends */ + for(const depend of source.depends) { + if(!_template_promises[depend]) + throw "Missing dependency " + depend; + await _template_promises[depend]; + } + await load_template_url(source.url + (options.cache_tag || "")); + } +} + +export type MultipleOptions = Options | ParallelOptions; +export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise { + const result = await load_parallel(paths, e => load(e, options), e => script_name(e, false), options); + if(result.failed.length > 0) { + if(config.error) { + console.error("Failed to load the following template files:"); + for(const style of result.failed) { + const sname = script_name(style.request, false); + if(style.error instanceof LoadSyntaxError) { + console.log(" - %s: %o", sname, style.error.source); + } else { + console.log(" - %s: %o", sname, style.error); + } + } + } + + critical_error("Failed to load template file " + script_name(result.failed[0].request, true) + "
" + "View the browser console for more information!"); + throw "failed to load template file " + script_name(result.failed[0].request, false); + } +} \ No newline at end of file diff --git a/loader/app/loader/utils.ts b/loader/app/loader/utils.ts new file mode 100644 index 00000000..fdab50f2 --- /dev/null +++ b/loader/app/loader/utils.ts @@ -0,0 +1,65 @@ +import {SourcePath} from "./loader"; +import {Options} from "./script_loader"; + +export class LoadSyntaxError { + readonly source: any; + constructor(source: any) { + this.source = source; + } +} + +export function script_name(path: SourcePath, html: boolean) { + if(Array.isArray(path)) { + let buffer = ""; + let _or = " or "; + for(let entry of path) + buffer += _or + script_name(entry, html); + return buffer.slice(_or.length); + } else if(typeof(path) === "string") + return html ? "" + path + "" : path; + else + return html ? "" + path.url + "" : path.url; +} + +export interface ParallelOptions extends Options { + max_parallel_requests?: number +} + +export interface ParallelResult { + succeeded: T[]; + failed: { + request: T, + error: T + }[], + + skipped: T[]; +} + +export async function load_parallel(requests: T[], executor: (_: T) => Promise, stringify: (_: T) => string, options: ParallelOptions) : Promise> { + const result: ParallelResult = { failed: [], succeeded: [], skipped: [] }; + const pending_requests = requests.slice(0).reverse(); /* we're only able to pop from the back */ + const current_requests = {}; + + while (pending_requests.length > 0) { + while(typeof options.max_parallel_requests !== "number" || options.max_parallel_requests <= 0 || Object.keys(current_requests).length < options.max_parallel_requests) { + const script = pending_requests.pop(); + const name = stringify(script); + + current_requests[name] = executor(script).catch(e => result.failed.push({ request: script, error: e })).then(() => { + delete current_requests[name]; + }); + if(pending_requests.length == 0) break; + } + + /* + * Wait 'till a new "slot" for downloading is free. + * This should also not throw because any errors will be caught before. + */ + await Promise.race(Object.keys(current_requests).map(e => current_requests[e])); + if(result.failed.length > 0) + break; /* finish loading the other requests and than show the error */ + } + await Promise.all(Object.keys(current_requests).map(e => current_requests[e])); + result.skipped.push(...pending_requests); + return result; +} \ No newline at end of file diff --git a/loader/exports/loader.d.ts b/loader/exports/loader.d.ts index a7a151ec..d947c9df 100644 --- a/loader/exports/loader.d.ts +++ b/loader/exports/loader.d.ts @@ -73,12 +73,6 @@ export type DependSource = { depends: string[]; } export type SourcePath = string | DependSource | string[]; -export function load_script(path: SourcePath) : Promise; -export function load_scripts(paths: SourcePath[]) : Promise; -export function load_style(path: SourcePath) : Promise; -export function load_styles(paths: SourcePath[]) : Promise; -export function load_template(path: SourcePath) : Promise; -export function load_templates(paths: SourcePath[]) : Promise; export type ErrorHandler = (message: string, detail: string) => void; export function critical_error(message: string, detail?: string); export function critical_error_handler(handler?: ErrorHandler, override?: boolean); diff --git a/loader/webpack.config.js b/loader/webpack.config.js index 075fb34c..baddf2d6 100644 --- a/loader/webpack.config.js +++ b/loader/webpack.config.js @@ -54,9 +54,9 @@ module.exports = { }, output: { filename: 'loader.js', - path: path.resolve(__dirname, '../dist'), + path: path.resolve(__dirname, 'dist'), library: "loader", - //libraryTarget: "umd" //"var" | "assign" | "this" | "window" | "self" | "global" | "commonjs" | "commonjs2" | "commonjs-module" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system" + libraryTarget: "window" //"var" | "assign" | "this" | "window" | "self" | "global" | "commonjs" | "commonjs2" | "commonjs-module" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system" }, optimization: { } }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index df6a8f9d..b30fd67e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "dev": true + }, "@types/emscripten": { "version": "1.39.2", "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.2.tgz", @@ -31,6 +37,23 @@ } } }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/html-minifier-terser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz", + "integrity": "sha512-q95SP4FdkmF0CwO0F2q0H6ZgudsApaY/yCtAQNRn1gduef5fGpyEphzy0YCq/N0UFvDSnLg5V8jFK/YGXlDiCw==", + "dev": true + }, "@types/jquery": { "version": "3.3.34", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.34.tgz", @@ -46,6 +69,12 @@ "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", "dev": true }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/moment": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", @@ -101,6 +130,52 @@ "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", "dev": true }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/tapable": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz", + "integrity": "sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", + "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, + "@types/webpack": { + "version": "4.41.9", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.9.tgz", + "integrity": "sha512-R68AotLGtaVL6HGfZRvEyYKsWcMv0CBFfSr4gxoYzhMn3LnjLV/ksP4dNi9de8dVG+Dn/GuDr1NwB/sDApB3pA==", + "dev": true, + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" + } + }, + "@types/webpack-sources": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.7.tgz", + "integrity": "sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + } + }, "@types/websocket": { "version": "0.0.40", "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-0.0.40.tgz", @@ -304,12 +379,28 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, "acorn": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, + "acorn-walk": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", + "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", + "dev": true + }, "ajv": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", @@ -468,6 +559,12 @@ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -528,6 +625,21 @@ } } }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, "array-unique": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", @@ -617,6 +729,12 @@ "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", "dev": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, "async-settle": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", @@ -755,6 +873,18 @@ "tweetnacl": "^0.14.3" } }, + "bfj": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", + "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "check-types": "^8.0.3", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -798,6 +928,38 @@ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -931,6 +1093,12 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, "cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -1002,6 +1170,16 @@ } } }, + "camel-case": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", + "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", + "dev": true, + "requires": { + "pascal-case": "^3.1.1", + "tslib": "^1.10.0" + } + }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", @@ -1045,6 +1223,12 @@ "supports-color": "^2.0.0" } }, + "check-types": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", + "dev": true + }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -1077,6 +1261,15 @@ "tslib": "^1.9.0" } }, + "chunk-manifest-webpack-plugin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chunk-manifest-webpack-plugin/-/chunk-manifest-webpack-plugin-1.1.2.tgz", + "integrity": "sha1-szLvuXwaI7/Tyl3O4TqhIN9y31k=", + "dev": true, + "requires": { + "webpack-core": "^0.6.9" + } + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -1139,6 +1332,16 @@ "source-map": "~0.6.0" } }, + "clean-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", + "dev": true, + "requires": { + "@types/webpack": "^4.4.31", + "del": "^4.1.1" + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -1320,6 +1523,21 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, "convert-hex": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/convert-hex/-/convert-hex-0.1.0.tgz", @@ -1341,6 +1559,18 @@ "integrity": "sha1-ec5BqbsNA7z3LNxqjzxW+7xkQQo=", "dev": true }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -1477,6 +1707,36 @@ } } }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + } + } + }, "css-tree": { "version": "1.0.0-alpha.29", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", @@ -1495,6 +1755,12 @@ } } }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1676,6 +1942,29 @@ } } }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1688,6 +1977,12 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -1698,6 +1993,12 @@ "minimalistic-assert": "^1.0.0" } }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -1715,12 +2016,73 @@ "randombytes": "^2.0.0" } }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", + "dev": true, + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, + "dot-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", + "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", + "dev": true, + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -1753,6 +2115,18 @@ "safer-buffer": "^2.1.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true + }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", @@ -1780,6 +2154,12 @@ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -1800,6 +2180,12 @@ "tapable": "^1.0.0" } }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -1818,6 +2204,36 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es5-ext": { "version": "0.10.53", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", @@ -1862,6 +2278,12 @@ "es6-symbol": "^3.1.1" } }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1893,6 +2315,12 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, "events": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", @@ -1966,6 +2394,70 @@ "homedir-polyfill": "^1.0.1" } }, + "exports-loader": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-0.7.0.tgz", + "integrity": "sha512-RKwCrO4A6IiKm0pG3c9V46JxIHcDplwwGJn6+JJ1RcVnh/WSGJa0xkmk5cRVtgOPzCAtTMGj2F7nluh9L0vpSA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "source-map": "0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz", + "integrity": "sha1-D+llA6yGpa213mP05BKuSHLNvoY=", + "dev": true + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, "ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -2055,6 +2547,44 @@ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, + "file-loader": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", + "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "json5": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", + "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -2068,6 +2598,12 @@ "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", "dev": true }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true + }, "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", @@ -2081,6 +2617,21 @@ "repeat-string": "^1.5.2" } }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -2466,6 +3017,12 @@ "mime-types": "^2.1.12" } }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -2475,6 +3032,12 @@ "map-cache": "^0.2.2" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -3639,6 +4202,19 @@ "which": "^1.2.14" } }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, "globule": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", @@ -3720,6 +4296,24 @@ "glogg": "^1.0.0" } }, + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3736,6 +4330,15 @@ "har-schema": "^2.0.0" } }, + "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-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -3843,6 +4446,12 @@ "minimalistic-assert": "^1.0.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -3863,12 +4472,132 @@ "parse-passwd": "^1.0.0" } }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true + }, "hosted-git-info": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, + "html-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-1.0.0.tgz", + "integrity": "sha512-acPyjP9Mo05jEbe/oejXu5gFwtKWdFewPoaVa47VCnHmRbR43jtdx/kPhPEbCBm2qWtIn+a8uTJAW4Ocnn4olw==", + "dev": true, + "requires": { + "html-minifier-terser": "^5.0.4", + "htmlparser2": "^4.1.0", + "loader-utils": "^2.0.0", + "parse-srcset": "^1.0.2", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "json5": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", + "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "html-minifier-terser": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.0.5.tgz", + "integrity": "sha512-cBSFFghQh/uHcfSiL42KxxIRMF7A144+3E44xdlctIjxEmkEfCvouxNyFH2wysXk1fCGBPwtcr3hDWlGTfkDew==", + "dev": true, + "requires": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + } + } + }, + "html-webpack-plugin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.3.tgz", + "integrity": "sha512-XCm5MGK6Yv/ey30fbhqjKIGm1r6G1HxmNorJ/xUdL/Zj3mOLtb9ZHEEIw0e4h3VzgyUrp8szCJh3VN9z1LNx7A==", + "dev": true, + "requires": { + "@types/html-minifier-terser": "^5.0.0", + "@types/tapable": "^1.0.5", + "@types/webpack": "^4.41.8", + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.15", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + } + }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3886,6 +4615,15 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "icss-utils": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", @@ -3984,6 +4722,12 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -4024,6 +4768,12 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -4033,6 +4783,12 @@ "kind-of": "^3.0.2" } }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -4118,6 +4874,30 @@ "kind-of": "^3.0.2" } }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -4153,6 +4933,15 @@ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "dev": true }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -4168,6 +4957,15 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -4459,6 +5257,15 @@ "signal-exit": "^3.0.0" } }, + "lower-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", + "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", + "dev": true, + "requires": { + "tslib": "^1.10.0" + } + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -4872,6 +5679,12 @@ "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", "dev": true }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", @@ -4919,6 +5732,18 @@ } } }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", @@ -4950,6 +5775,12 @@ "brorand": "^1.0.1" } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, "mime-db": { "version": "1.43.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", @@ -5160,6 +5991,12 @@ } } }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", @@ -5178,6 +6015,16 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "no-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", + "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", + "dev": true, + "requires": { + "lower-case": "^2.0.1", + "tslib": "^1.10.0" + } + }, "node-gyp": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", @@ -5342,6 +6189,15 @@ "set-blocking": "~2.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -5381,6 +6237,12 @@ } } }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -5445,6 +6307,28 @@ } } }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "object.map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", @@ -5514,6 +6398,15 @@ } } }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5523,6 +6416,12 @@ "wrappy": "1" } }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "dev": true + }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -5605,6 +6504,12 @@ "p-limit": "^2.0.0" } }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -5628,6 +6533,16 @@ "readable-stream": "^2.1.5" } }, + "param-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", + "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", + "dev": true, + "requires": { + "dot-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, "parse-asn1": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", @@ -5686,6 +6601,28 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascal-case": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz", + "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", + "dev": true, + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -5719,6 +6656,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -5746,6 +6689,12 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -5952,6 +6901,16 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -5986,6 +6945,16 @@ "react-is": "^16.8.1" } }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -6117,6 +7086,24 @@ "safe-buffer": "^5.1.0" } }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "react": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", @@ -6504,6 +7491,12 @@ "safe-regex": "^1.1.0" } }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -6531,6 +7524,77 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, + "renderkid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", + "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + }, + "dependencies": { + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", @@ -6932,12 +7996,53 @@ "sver-compat": "^1.5.0" } }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "serialize-javascript": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", "dev": true }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6973,6 +8078,12 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", "dev": true }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -7295,6 +8406,12 @@ } } }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, "stdout-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", @@ -7366,6 +8483,48 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.trimend": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", + "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", + "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -7594,6 +8753,12 @@ "through2": "^2.0.3" } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -7619,6 +8784,12 @@ "glob": "^7.1.2" } }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "dev": true + }, "ts-loader": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.2.tgz", @@ -7754,6 +8925,16 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -7847,6 +9028,12 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -7961,6 +9148,28 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -7998,6 +9207,12 @@ "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", "dev": true }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -8800,6 +10015,64 @@ } } }, + "webpack-bundle-analyzer": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.1.tgz", + "integrity": "sha512-Nfd8HDwfSx1xBwC+P8QMGvHAOITxNBSvu/J/mCJvOwv+G4VWkU7zir9SSenTtyCi0LnVtmsc7G5SZo1uV+bxRw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.15", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.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" + } + }, + "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" + } + } + } + }, "webpack-cli": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz", @@ -9086,6 +10359,58 @@ } } }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "webpack-manifest-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz", + "integrity": "sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==", + "dev": true, + "requires": { + "fs-extra": "^7.0.0", + "lodash": ">=3.5 <5", + "object.entries": "^1.1.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, "webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", @@ -9129,6 +10454,15 @@ "errno": "~0.1.7" } }, + "worker-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-4.0.2.tgz", + "integrity": "sha512-V+1zSZMOOKk+uBzKyNIODLQLsx59zSIOaI75J1EMS0iR1qy+KQR3y/pQ3T0vIhvPfDFapGRMsoMvQNEL3okqSA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -9145,6 +10479,15 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index b7c8f5ca..7008ffd1 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,17 @@ "@types/react-dom": "^16.9.5", "@types/sha256": "^0.2.0", "@types/websocket": "0.0.40", + "chunk-manifest-webpack-plugin": "^1.1.2", "clean-css": "^4.2.1", + "clean-webpack-plugin": "^3.0.0", "css-loader": "^3.4.2", "csso-cli": "^2.0.2", + "exports-loader": "^0.7.0", + "file-loader": "^6.0.0", "fs-extra": "latest", "gulp": "^4.0.2", + "html-loader": "^1.0.0", + "html-webpack-plugin": "^4.0.3", "mime-types": "^2.1.24", "mini-css-extract-plugin": "^0.9.0", "mkdirp": "^0.5.1", @@ -53,7 +59,10 @@ "typescript": "3.6.5", "wat2wasm": "^1.0.2", "webpack": "^4.42.1", - "webpack-cli": "^3.3.11" + "webpack-bundle-analyzer": "^3.6.1", + "webpack-cli": "^3.3.11", + "webpack-manifest-plugin": "^2.2.0", + "worker-plugin": "^4.0.2" }, "repository": { "type": "git", diff --git a/shared/popup/certaccept/js/main.ts b/shared/popup/certaccept/js/main.ts index 003129be..2d9abd48 100644 --- a/shared/popup/certaccept/js/main.ts +++ b/shared/popup/certaccept/js/main.ts @@ -1,5 +1,6 @@ import {settings, Settings} from "tc-shared/settings"; import * as loader from "tc-loader"; +import * as log from "tc-shared/log"; import {LogCategory} from "tc-shared/log"; import * as bipc from "tc-shared/BrowserIPC"; diff --git a/tsconfig.json b/tsconfig.json index 9bd65288..143b61c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "paths": { "*": ["shared/declarations/*"], "tc-shared/*": ["shared/js/*"], + "tc-backend/web/*": ["web/js/*"], /* specific web part */ "tc-backend/*": ["shared/backend.d/*"], "tc-loader": ["loader/exports/loader.d.ts"] } diff --git a/web/js/codec/CodecWrapperRaw.ts b/web/js/codec/CodecRaw.ts similarity index 96% rename from web/js/codec/CodecWrapperRaw.ts rename to web/js/codec/CodecRaw.ts index b6b591f1..df2bde16 100644 --- a/web/js/codec/CodecWrapperRaw.ts +++ b/web/js/codec/CodecRaw.ts @@ -1,6 +1,6 @@ import {BasicCodec} from "./BasicCodec"; -export class CodecWrapperRaw extends BasicCodec { +export class CodecRaw extends BasicCodec { converterRaw: any; converter: Uint8Array; bufferSize: number = 4096 * 4; diff --git a/web/js/codec/CodecWrapperWorker.ts b/web/js/codec/CodecWrapperWorker.ts index ea6c42ed..7350bbdb 100644 --- a/web/js/codec/CodecWrapperWorker.ts +++ b/web/js/codec/CodecWrapperWorker.ts @@ -1,135 +1,37 @@ import {BasicCodec} from "./BasicCodec"; import {CodecType} from "./Codec"; -import {LogCategory} from "tc-shared/log"; import * as log from "tc-shared/log"; -import {settings} from "tc-shared/settings"; +import {LogCategory} from "tc-shared/log"; + +interface ExecuteResult { + result?: any; + error?: string; + + success: boolean; + + timings: { + upstream: number; + downstream: number; + handle: number; + } +} export class CodecWrapperWorker extends BasicCodec { private _worker: Worker; - private _workerListener: {token: string, resolve: (data: any) => void}[] = []; - private _workerCallbackToken = "callback_token"; - private _workerTokeIndex: number = 0; - type: CodecType; - private _initialized: boolean = false; - private _workerCallbackResolve: () => any; - private _workerCallbackReject: ($: any) => any; + private _initialize_promise: Promise; - private _initializePromise: Promise; - name(): string { - return "Worker for " + CodecType[this.type] + " Channels " + this.channelCount; - } + private _token_index: number = 0; + readonly type: CodecType; - initialise() : Promise { - if(this._initializePromise) return this._initializePromise; - return this._initializePromise = this.spawnWorker().then(() => new Promise((resolve, reject) => { - const token = this.generateToken(); - this.sendWorkerMessage({ - command: "initialise", - type: this.type, - channelCount: this.channelCount, - token: token - }); + private pending_executes: {[key: string]: { + timeout?: any; - this._workerListener.push({ - token: token, - resolve: data => { - this._initialized = data["success"] == true; - if(data["success"] == true) - resolve(); - else - reject(data.message); - } - }) - })); - } + timestamp_send: number, - initialized() : boolean { - return this._initialized; - } - - deinitialise() { - this.sendWorkerMessage({ - command: "deinitialise" - }); - } - - decode(data: Uint8Array): Promise { - let token = this.generateToken(); - let result = new Promise((resolve, reject) => { - this._workerListener.push( - { - token: token, - resolve: (data) => { - if(data.success) { - let array = new Float32Array(data.dataLength); - for(let index = 0; index < array.length; index++) - array[index] = data.data[index]; - - let audioBuf = this._audioContext.createBuffer(this.channelCount, array.length / this.channelCount, this._codecSampleRate); - for (let channel = 0; channel < this.channelCount; channel++) { - for (let offset = 0; offset < audioBuf.length; offset++) { - audioBuf.getChannelData(channel)[offset] = array[channel + offset * this.channelCount]; - } - } - resolve(audioBuf); - } else { - reject(data.message); - } - } - } - ); - }); - this.sendWorkerMessage({ - command: "decodeSamples", - token: token, - data: data, - dataLength: data.length - }); - return result; - } - - encode(data: AudioBuffer) : Promise { - let token = this.generateToken(); - let result = new Promise((resolve, reject) => { - this._workerListener.push( - { - token: token, - resolve: (data) => { - if(data.success) { - let array = new Uint8Array(data.dataLength); - for(let index = 0; index < array.length; index++) - array[index] = data.data[index]; - resolve(array); - } else { - reject(data.message); - } - } - } - ); - }); - - let buffer = new Float32Array(this.channelCount * data.length); - for (let offset = 0; offset < data.length; offset++) { - for (let channel = 0; channel < this.channelCount; channel++) - buffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset]; - } - - this.sendWorkerMessage({ - command: "encodeSamples", - token: token, - data: buffer, - dataLength: buffer.length - }); - return result; - } - - reset() : boolean { - this.sendWorkerMessage({ - command: "reset" - }); - return true; - } + resolve: (_: ExecuteResult) => void; + reject: (_: any) => void; + }} = {}; constructor(type: CodecType) { super(48000); @@ -146,35 +48,92 @@ export class CodecWrapperWorker extends BasicCodec { } } - private generateToken() { - return this._workerTokeIndex++ + "_token"; + name(): string { + return "Worker for " + CodecType[this.type] + " Channels " + this.channelCount; } - private sendWorkerMessage(message: any, transfare?: any[]) { - message["timestamp"] = Date.now(); - this._worker.postMessage(message, transfare as any); + async initialise() : Promise { + if(this._initialized) return; + + this._initialize_promise = this.spawn_worker().then(() => this.execute("initialise", { + type: this.type, + channelCount: this.channelCount, + })).then(result => { + if(result.success) + return Promise.resolve(true); + + log.error(LogCategory.VOICE, tr("Failed to initialize codec %s: %s"), CodecType[this.type], result.error); + return Promise.reject(result.error); + }); + + this._initialized = true; + await this._initialize_promise; } - private onWorkerMessage(message: any) { + initialized() : boolean { + return this._initialized; + } + + deinitialise() { + this.execute("deinitialise", {}); + this._initialized = false; + this._initialize_promise = undefined; + } + + async decode(data: Uint8Array): Promise { + const result = await this.execute("decodeSamples", { data: data, length: data.length }); + if(result.timings.downstream > 5 || result.timings.upstream > 5 || result.timings.handle > 5) + log.warn(LogCategory.VOICE, tr("Worker message stock time: {downstream: %dms, handle: %dms, upstream: %dms}"), result.timings.downstream, result.timings.handle, result.timings.upstream); + + if(!result.success) throw result.error || tr("unknown decode error"); + + let array = new Float32Array(result.result.length); + for(let index = 0; index < array.length; index++) + array[index] = result.result.data[index]; + + let audioBuf = this._audioContext.createBuffer(this.channelCount, array.length / this.channelCount, this._codecSampleRate); + for (let channel = 0; channel < this.channelCount; channel++) { + for (let offset = 0; offset < audioBuf.length; offset++) { + audioBuf.getChannelData(channel)[offset] = array[channel + offset * this.channelCount]; + } + } + + return audioBuf; + } + + async encode(data: AudioBuffer) : Promise { + let buffer = new Float32Array(this.channelCount * data.length); + for (let offset = 0; offset < data.length; offset++) { + for (let channel = 0; channel < this.channelCount; channel++) + buffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset]; + } + + const result = await this.execute("encodeSamples", { data: buffer, length: buffer.length }); + if(result.timings.downstream > 5 || result.timings.upstream > 5) + log.warn(LogCategory.VOICE, tr("Worker message stock time: {downstream: %dms, handle: %dms, upstream: %dms}"), result.timings.downstream, result.timings.handle, result.timings.upstream); + if(!result.success) throw result.error || tr("unknown encode error"); + + let array = new Uint8Array(result.result.length); + for(let index = 0; index < array.length; index++) + array[index] = result.result.data[index]; + return array; + } + + reset() : boolean { + //TODO: Await result! + this.execute("reset", {}); + return true; + } + + private handle_worker_message(message: any) { if(!message["token"]) { log.error(LogCategory.VOICE, tr("Invalid worker token!")); return; } - if(message["token"] == this._workerCallbackToken) { - if(message["type"] == "loaded") { - log.info(LogCategory.VOICE, tr("[Codec] Got worker init response: Success: %o Message: %o"), message["success"], message["message"]); - if(message["success"]) { - if(this._workerCallbackResolve) - this._workerCallbackResolve(); - } else { - if(this._workerCallbackReject) - this._workerCallbackReject(message["message"]); - } - this._workerCallbackReject = undefined; - this._workerCallbackResolve = undefined; - return; - } else if(message["type"] == "chatmessage_server") { + if(message["token"] === "notify") { + /* currently not really used */ + if(message["type"] == "chatmessage_server") { //FIXME? return; } @@ -182,29 +141,69 @@ export class CodecWrapperWorker extends BasicCodec { return; } - /* lets warn on general packets. Control packets are allowed to "stuck" a bit longer */ - if(Date.now() - message["timestamp"] > 5) - log.warn(LogCategory.VOICE, tr("Worker message stock time: %d"), Date.now() - message["timestamp"]); + const request = this.pending_executes[message["token"]]; + if(typeof request !== "object") { + log.error(LogCategory.VOICE, tr("Received worker execute result for unknown token (%s)"), message["token"]); + return; + } + delete this.pending_executes[message["token"]]; - for(let entry of this._workerListener) { - if(entry.token == message["token"]) { - entry.resolve(message); - this._workerListener.remove(entry); - return; + const result: ExecuteResult = { + success: message["success"], + error: message["error"], + result: message["result"], + timings: { + downstream: message["timestamp_received"] - request.timestamp_send, + handle: message["timestamp_send"] - message["timestamp_received"], + upstream: Date.now() - message["timestamp_send"] } + }; + clearTimeout(request.timeout); + request.resolve(result); + } + + private handle_worker_error(error: any) { + log.error(LogCategory.VOICE, tr("Received error from codec worker. Closing worker.")); + for(const token of Object.keys(this.pending_executes)) { + this.pending_executes[token].reject(error); + delete this.pending_executes[token]; } - log.error(LogCategory.VOICE, tr("Could not find worker token entry! (%o)"), message["token"]); + this._worker = undefined; } - private spawnWorker() : Promise { - return new Promise((resolve, reject) => { - this._workerCallbackReject = reject; - this._workerCallbackResolve = resolve; + private execute(command: string, data: any, timeout?: number) : Promise { + return new Promise((resolve, reject) => { + if(!this._worker) { + reject(tr("worker does not exists")); + return; + } - this._worker = new Worker(settings.static("worker_directory", "js/workers/") + "WorkerCodec.js"); - this._worker.onmessage = event => this.onWorkerMessage(event.data); - this._worker.onerror = (error: ErrorEvent) => reject("Failed to load worker (" + error.message + ")"); //TODO tr + const token = this._token_index++ + "_token"; + + const payload = { + token: token, + command: command, + data: data, + }; + + this.pending_executes[token] = { + timeout: typeof timeout === "number" ? setTimeout(() => reject(tr("timeout for command ") + command), timeout) : undefined, + resolve: resolve, + reject: reject, + timestamp_send: Date.now() + }; + + this._worker.postMessage(payload); }); } + + private async spawn_worker() : Promise { + this._worker = new Worker("tc-backend/web/workers/codec", { type: "module" }); + this._worker.onmessage = event => this.handle_worker_message(event.data); + this._worker.onerror = event => this.handle_worker_error(event.error); + + const result = await this.execute("global-initialize", {}, 15000); + if(!result.success) throw result.error; + } } \ No newline at end of file diff --git a/web/js/workers/codec/CodecWorker.ts b/web/js/workers/codec/CodecWorker.ts index 1563e1c5..2b88482f 100644 --- a/web/js/workers/codec/CodecWorker.ts +++ b/web/js/workers/codec/CodecWorker.ts @@ -1,7 +1,8 @@ -const prefix = "[CodecWorker] "; -const workerCallbackToken = "callback_token"; +import {CodecType} from "tc-backend/web/codec/Codec"; -interface CodecWorker { +const prefix = "[CodecWorker] "; + +export interface CodecWorker { name(); initialise?() : string; deinitialise(); @@ -11,78 +12,17 @@ interface CodecWorker { reset(); } -let codecInstance: CodecWorker; +let supported_types = {}; +export function register_codec(type: CodecType, allocator: (options?: any) => Promise) { + supported_types[type] = allocator; +} -onmessage = function(e: MessageEvent) { - let data = e.data; +let initialize_callback: () => Promise; +export function set_initialize_callback(callback: () => Promise) { + initialize_callback = callback; +} - let res: any = {}; - res.token = data.token; - res.success = false; - - //console.log(prefix + " Got from main: %o", data); - switch (data.command) { - case "initialise": - let error; - console.log(prefix + "Got initialize for type " + CodecType[data.type as CodecType]); - switch (data.type as CodecType) { - case CodecType.OPUS_MUSIC: - codecInstance = new OpusWorker(2, OpusType.AUDIO); - break; - case CodecType.OPUS_VOICE: - codecInstance = new OpusWorker(1, OpusType.VOIP); - break; - default: - error = "Could not find worker type!"; - console.error("Could not resolve opus type!"); - break; - } - - error = error || codecInstance.initialise(); - if(error) - res["message"] = error; - else - res["success"] = true; - break; - case "encodeSamples": - let encodeArray = new Float32Array(data.dataLength); - for(let index = 0; index < encodeArray.length; index++) - encodeArray[index] = data.data[index]; - - let encodeResult = codecInstance.encode(encodeArray); - - if(typeof encodeResult === "string") { - res.message = encodeResult; - } else { - res.success = true; - res.data = encodeResult; - res.dataLength = encodeResult.length; - } - break; - case "decodeSamples": - let decodeArray = new Uint8Array(data.dataLength); - for(let index = 0; index < decodeArray.length; index++) - decodeArray[index] = data.data[index]; - - let decodeResult = codecInstance.decode(decodeArray); - - if(typeof decodeResult === "string") { - res.message = decodeResult; - } else { - res.success = true; - res.data = decodeResult; - res.dataLength = decodeResult.length; - } - break; - case "reset": - codecInstance.reset(); - break; - default: - console.error(prefix + "Unknown type " + data.command); - } - - if(res.token && res.token.length > 0) sendMessage(res, e.origin); -}; +export let codecInstance: CodecWorker; function printMessageToServerTab(message: string) { /* @@ -95,7 +35,105 @@ function printMessageToServerTab(message: string) { } declare function postMessage(message: any): void; -function sendMessage(message: any, origin?: string){ +function sendMessage(message: any, origin?: string) { message["timestamp"] = Date.now(); postMessage(message); -} \ No newline at end of file +} + +let globally_initialized = false; +let global_initialize_result; + +/** + * @param command + * @param data + * @return string on error or object on success + */ +async function handle_message(command: string, data: any) : Promise { + switch (command) { + case "global-initialize": + const init_result = globally_initialized ? global_initialize_result : await initialize_callback(); + globally_initialized = true; + + if(typeof init_result === "string") + return init_result; + + return {}; + case "initialise": + console.log(prefix + "Got initialize for type " + CodecType[data.type as CodecType]); + if(!supported_types[data.type]) + return "type unsupported"; + + try { + codecInstance = await supported_types[data.type](data.options); + } catch(ex) { + console.error(prefix + "Failed to allocate codec: %o", ex); + return typeof ex === "string" ? ex : "failed to allocate codec"; + } + + const error = codecInstance.initialise(); + if(error) return error; + + return {}; + case "encodeSamples": + let encodeArray = new Float32Array(data.length); + for(let index = 0; index < encodeArray.length; index++) + encodeArray[index] = data.data[index]; + + let encodeResult = codecInstance.encode(encodeArray); + if(typeof encodeResult === "string") + return encodeResult; + else + return { data: encodeResult, length: encodeResult.length }; + case "decodeSamples": + let decodeArray = new Uint8Array(data.length); + for(let index = 0; index < decodeArray.length; index++) + decodeArray[index] = data.data[index]; + + let decodeResult = codecInstance.decode(decodeArray); + if(typeof decodeResult === "string") + return decodeResult; + else + return { data: decodeResult, length: decodeResult.length }; + case "reset": + codecInstance.reset(); + break; + default: + return "unknown command"; + } +} + + +const handle_message_event = (e: MessageEvent) => { + const token = e.data.token; + const received = Date.now(); + + const send_result = result => { + const data = {}; + if(typeof result === "object") { + data["result"] = result; + data["success"] = true; + } else if(typeof result === "string") { + data["error"] = result; + data["success"] = false; + } else { + data["error"] = "invalid result"; + data["success"] = false; + } + data["token"] = token; + data["timestamp_received"] = received; + data["timestamp_send"] = Date.now(); + + sendMessage(data, e.origin); + }; + handle_message(e.data.command, e.data.data).then(res => { + if(token) { + send_result(res); + } + }).catch(error => { + console.warn("An error has been thrown while handing command %s: %o", e.data.command, error); + if(token) { + send_result(typeof error === "string" ? error : "unexpected exception has been thrown"); + } + }); +}; +addEventListener("message", handle_message_event); \ No newline at end of file diff --git a/web/js/workers/codec/OpusCodec.ts b/web/js/workers/codec/OpusCodec.ts index 9bcf391a..b89fbc5c 100644 --- a/web/js/workers/codec/OpusCodec.ts +++ b/web/js/workers/codec/OpusCodec.ts @@ -1,78 +1,82 @@ -/// +import * as cworker from "./CodecWorker"; +import {CodecType} from "tc-backend/web/codec/Codec"; +import {CodecWorker} from "./CodecWorker"; + +const prefix = "OpusWorker"; + +declare global { + interface Window { + __init_em_module: ((Module: any) => void)[]; + } +} +self.__init_em_module = self.__init_em_module || []; const WASM_ERROR_MESSAGES = [ 'no native wasm support detected' ]; -this["Module"] = this["Module"] || ({} as any); /* its required to cast {} to any!*/ +let Module; +self.__init_em_module.push(m => Module = m); +const runtime_initialize_promise = new Promise((resolve, reject) => { + self.__init_em_module.push(Module => { + const cleanup = () => { + Module['onRuntimeInitialized'] = undefined; + Module['onAbort'] = undefined; + }; -let initialized = false; -Module['onRuntimeInitialized'] = function() { - initialized = true; - console.log(prefix + "Initialized!"); + Module['onRuntimeInitialized'] = () => { + cleanup(); + resolve(); + }; - sendMessage({ - token: workerCallbackToken, - type: "loaded", - success: true - }) -}; + Module['onAbort'] = error => { + cleanup(); + + let message; + if(error instanceof DOMException) + message = "DOMException (" + error.name + "): " + error.code + " => " + error.message; + else { + abort_message = error; + message = error; + if(error.indexOf("no binaryen method succeeded") != -1) { + for(const error of WASM_ERROR_MESSAGES) { + if(last_error_message.indexOf(error) != -1) { + message = "no native wasm support detected, but its required"; + break; + } + } + } + } + + reject(message); + } + }); +}); let abort_message: string = undefined; let last_error_message: string; +self.__init_em_module.push(Module => { + Module['print'] = function() { + if(arguments.length == 1 && arguments[0] == abort_message) + return; /* we don't need to reprint the abort message! */ -Module['print'] = function() { - if(arguments.length == 1 && arguments[0] == abort_message) - return; /* we don't need to reprint the abort message! */ - console.log(...arguments); -}; + console.log("Print: ", ...arguments); + }; -Module['printErr'] = function() { - if(arguments.length == 1 && arguments[0] == abort_message) - return; /* we don't need to reprint the abort message! */ + Module['printErr'] = function() { + if(arguments.length == 1 && arguments[0] == abort_message) + return; /* we don't need to reprint the abort message! */ - last_error_message = arguments[0]; - for(const suppress of WASM_ERROR_MESSAGES) - if((arguments[0] as string).indexOf(suppress) != -1) - return; + last_error_message = arguments[0]; + for(const suppress of WASM_ERROR_MESSAGES) + if((arguments[0] as string).indexOf(suppress) != -1) + return; - console.error(...arguments); -}; + console.error("Error: ",...arguments); + }; -Module['onAbort'] = (message: string | DOMException) => { - /* no native wasm support detected */ - Module['onAbort'] = undefined; - - if(message instanceof DOMException) - message = "DOMException (" + message.name + "): " + message.code + " => " + message.message; - else { - abort_message = message; - if(message.indexOf("no binaryen method succeeded") != -1) - for(const error of WASM_ERROR_MESSAGES) - if(last_error_message.indexOf(error) != -1) { - message = "no native wasm support detected, but its required"; - break; - } - } - - sendMessage({ - token: workerCallbackToken, - type: "loaded", - success: false, - message: message - }); -}; - -try { - console.log("Node init!"); Module['locateFile'] = file => "../../wasm/" + file; - importScripts("../../wasm/TeaWeb-Worker-Codec-Opus.js"); -} catch (e) { - if(typeof(Module['onAbort']) === "function") { - console.log(e); - Module['onAbort']("Failed to load native scripts"); - } /* else the error had been already handled because its a WASM error */ -} +}); enum OpusType { VOIP = 2048, @@ -89,6 +93,7 @@ class OpusWorker implements CodecWorker { private fn_decode: any; private fn_encode: any; private fn_reset: any; + private fn_error_message: any; private bufferSize = 4096 * 2; private encodeBufferRaw: any; @@ -106,11 +111,12 @@ class OpusWorker implements CodecWorker { } initialise?() : string { - this.fn_newHandle = cwrap("codec_opus_createNativeHandle", "number", ["number", "number"]); - this.fn_decode = cwrap("codec_opus_decode", "number", ["number", "number", "number", "number"]); + this.fn_newHandle = Module.cwrap("codec_opus_createNativeHandle", "number", ["number", "number"]); + this.fn_decode = Module.cwrap("codec_opus_decode", "number", ["number", "number", "number", "number"]); /* codec_opus_decode(handle, buffer, length, maxlength) */ - this.fn_encode = cwrap("codec_opus_encode", "number", ["number", "number", "number", "number"]); - this.fn_reset = cwrap("codec_opus_reset", "number", ["number"]); + this.fn_encode = Module.cwrap("codec_opus_encode", "number", ["number", "number", "number", "number"]); + this.fn_reset = Module.cwrap("codec_opus_reset", "number", ["number"]); + this.fn_error_message = Module.cwrap("opus_error_message", "string", ["number"]); this.nativeHandle = this.fn_newHandle(this.channelCount, this.type); @@ -127,12 +133,8 @@ class OpusWorker implements CodecWorker { decode(data: Uint8Array): Float32Array | string { if (data.byteLength > this.decodeBuffer.byteLength) return "Data to long!"; this.decodeBuffer.set(data); - //console.log("decode(" + data.length + ")"); - //console.log(data); let result = this.fn_decode(this.nativeHandle, this.decodeBuffer.byteOffset, data.byteLength, this.decodeBuffer.byteLength); - if (result < 0) { - return "invalid result on decode (" + result + ")"; - } + if (result < 0) return this.fn_error_message(result); return Module.HEAPF32.slice(this.decodeBuffer.byteOffset / 4, (this.decodeBuffer.byteOffset / 4) + (result * this.channelCount)); } @@ -140,9 +142,7 @@ class OpusWorker implements CodecWorker { this.encodeBuffer.set(data); let result = this.fn_encode(this.nativeHandle, this.encodeBuffer.byteOffset, data.length, this.encodeBuffer.byteLength); - if (result < 0) { - return "invalid result on encode (" + result + ")"; - } + if (result < 0) return this.fn_error_message(result); let buf = Module.HEAP8.slice(this.encodeBuffer.byteOffset, this.encodeBuffer.byteOffset + result); return Uint8Array.from(buf); } @@ -151,4 +151,25 @@ class OpusWorker implements CodecWorker { console.log(prefix + " Reseting opus codec!"); this.fn_reset(this.nativeHandle); } -} \ No newline at end of file +} +cworker.register_codec(CodecType.OPUS_MUSIC, async () => new OpusWorker(2, OpusType.AUDIO)); +cworker.register_codec(CodecType.OPUS_VOICE, async () => new OpusWorker(1, OpusType.VOIP)); + +cworker.set_initialize_callback(async () => { + try { + require("tc-generated/codec/opus"); + } catch (e) { + if(Module) { + if(typeof(Module['onAbort']) === "function") { + Module['onAbort']("Failed to load native scripts"); + } /* else the error had been already handled because its a WASM error */ + } else { + throw e; + } + } + if(!Module) + throw "Missing module handle"; + + await runtime_initialize_promise; + return true; +}); \ No newline at end of file diff --git a/web/js/workers/codec/index.ts b/web/js/workers/codec/index.ts new file mode 100644 index 00000000..2cdf4199 --- /dev/null +++ b/web/js/workers/codec/index.ts @@ -0,0 +1 @@ +require("./OpusCodec"); \ No newline at end of file diff --git a/web/js/workers/tsconfig_worker_codec.json b/web/js/workers/tsconfig_worker_codec.json deleted file mode 100644 index f2b9fa21..00000000 --- a/web/js/workers/tsconfig_worker_codec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "module": "none", - "target": "es6", - "sourceMap": true, - "outFile": "WorkerCodec.js" - }, - "include": [ - "../../types/" - ], - "files": [ - "codec/CodecWorker.ts", - "codec/OpusCodec.ts", - "../codec/Codec.ts" - ] -} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 34b9cd30..55222cbb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,10 @@ const path = require('path'); +const webpack = require("webpack"); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const CircularDependencyPlugin = require('circular-dependency-plugin') +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const ManifestGenerator = require("./webpack/ManifestPlugin"); +const WorkerPlugin = require('worker-plugin'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const isDevelopment = process.env.NODE_ENV === 'development'; module.exports = { @@ -11,10 +15,16 @@ module.exports = { devtool: 'inline-source-map', mode: "development", plugins: [ + new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: isDevelopment ? '[name].css' : '[name].[hash].css', chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css' }), + new ManifestGenerator({ + file: path.join(__dirname, "dist/manifest.json") + }), + new WorkerPlugin(), + //new BundleAnalyzerPlugin() /* new CircularDependencyPlugin({ //exclude: /a\.js|node_modules/, @@ -23,6 +33,12 @@ module.exports = { cwd: process.cwd(), }) */ + /* + new webpack.optimize.AggressiveSplittingPlugin({ + minSize: 1024 * 128, + maxSize: 1024 * 1024 + }) + */ ], module: { rules: [ @@ -58,49 +74,28 @@ module.exports = { } } ] - }, + } ], }, resolve: { extensions: ['.tsx', '.ts', '.js', ".scss"], alias: { "tc-shared": path.resolve(__dirname, "shared/js"), - "tc-backend": path.resolve(__dirname, "web/js") + "tc-backend/web": path.resolve(__dirname, "web/js"), + "tc-backend": path.resolve(__dirname, "web/js"), + "tc-generated/codec/opus": path.resolve(__dirname, "asm/generated/TeaWeb-Worker-Codec-Opus.js"), //"tc-backend": path.resolve(__dirname, "shared/backend.d"), }, }, externals: { - "tc-loader": "umd loader" + "tc-loader": "window loader" }, output: { - filename: 'shared-app.js', + filename: '[contenthash].js', path: path.resolve(__dirname, 'dist'), - libraryTarget: "umd", - library: "shared" + publicPath: "js/" }, optimization: { - /* - splitChunks: { - chunks: 'async', - minSize: 1, - maxSize: 500000, - minChunks: 1, - maxAsyncRequests: 6, - maxInitialRequests: 4, - automaticNameDelimiter: '~', - automaticNameMaxLength: 30, - cacheGroups: { - defaultVendors: { - test: /[\\/]node_modules[\\/]/, - priority: -10 - }, - default: { - minChunks: 2, - priority: -20, - reuseExistingChunk: true - } - } - } - */ + splitChunks: { } } }; \ No newline at end of file diff --git a/webpack/ManifestPlugin.ts b/webpack/ManifestPlugin.ts new file mode 100644 index 00000000..c7381fa4 --- /dev/null +++ b/webpack/ManifestPlugin.ts @@ -0,0 +1,49 @@ +import * as webpack from "webpack"; +import * as fs from "fs"; + +interface Options { + file?: string; +} + +class ManifestGenerator { + private manifest_content; + + readonly options: Options; + constructor(options: Options) { + this.options = options || {}; + } + + apply(compiler: webpack.Compiler) { + compiler.hooks.afterCompile.tap(this.constructor.name, compilation => { + const chunks_data = {}; + for(const chunk_group of compilation.chunkGroups) { + console.log(chunk_group.options.name); + const js_files = []; + for(const chunk of chunk_group.chunks) { + if(chunk.files.length !== 1) throw "expected only one file per chunk"; + + const file = chunk.files[0]; + console.log("Chunk: %s - %s - %s", chunk.id, chunk.hash, file); + //console.log(chunk); + //console.log(" - %s - %o", chunk.id, chunk); + js_files.push({ + hash: chunk.hash, + file: file + }) + } + chunks_data[chunk_group.options.name] = js_files; + } + + this.manifest_content = { + version: 1, + chunks: chunks_data + }; + }); + + compiler.hooks.done.tap(this.constructor.name, () => { + fs.writeFileSync(this.options.file || "manifest.json", JSON.stringify(this.manifest_content)); + }); + } +} + +export = ManifestGenerator; \ No newline at end of file