Some more small rework and cleanup
This commit is contained in:
parent
e2be859266
commit
85acd6dd56
11 changed files with 202 additions and 255 deletions
|
@ -1,6 +1,6 @@
|
||||||
import * as loader from "./loader/loader";
|
import * as loader from "./loader/loader";
|
||||||
import {Stage} from "./loader/loader";
|
import {Stage} from "./loader/loader";
|
||||||
import {getUrlParameter} from "./loader/utils";
|
import {getUrlParameter} from "./loader/Utils";
|
||||||
|
|
||||||
let overlay: HTMLDivElement;
|
let overlay: HTMLDivElement;
|
||||||
let setupContainer: HTMLDivElement;
|
let setupContainer: HTMLDivElement;
|
||||||
|
|
99
loader/app/bootstrap.ts
Normal file
99
loader/app/bootstrap.ts
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import "core-js/stable";
|
||||||
|
import "./polifill";
|
||||||
|
import "./css";
|
||||||
|
|
||||||
|
import {ApplicationLoader} from "./loader/loader";
|
||||||
|
import {getUrlParameter} from "./loader/Utils";
|
||||||
|
|
||||||
|
/* let the loader register himself at the window first */
|
||||||
|
const target = getUrlParameter("loader-target") || "app";
|
||||||
|
console.info("Loading app with loader \"%s\"", target);
|
||||||
|
|
||||||
|
let appLoader: ApplicationLoader;
|
||||||
|
if(target === "empty") {
|
||||||
|
appLoader = new (require("./targets/empty").default);
|
||||||
|
} else if(target === "manifest") {
|
||||||
|
appLoader = new (require("./targets/maifest-target").default);
|
||||||
|
} else {
|
||||||
|
appLoader = new (require("./targets/app").default);
|
||||||
|
}
|
||||||
|
setTimeout(() => appLoader.execute(), 0);
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
if(__build.target === "client") {
|
||||||
|
/* do this so we don't get a react dev tools warning within the client */
|
||||||
|
if(!('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window)) {
|
||||||
|
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hello World message */
|
||||||
|
{
|
||||||
|
const clog = console.log;
|
||||||
|
const print_security = () => {
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 42px",
|
||||||
|
"font-weight: bold",
|
||||||
|
"-webkit-text-stroke: 2px black",
|
||||||
|
"color: red"
|
||||||
|
].join(";");
|
||||||
|
clog("%c ", "font-size: 100px;");
|
||||||
|
clog("%cSecurity warning:", css);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 18px",
|
||||||
|
"font-weight: bold"
|
||||||
|
].join(";");
|
||||||
|
|
||||||
|
clog("%cPasting anything in here could give attackers access to your data.", css);
|
||||||
|
clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css);
|
||||||
|
clog("%c ", "font-size: 100px;");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* print the hello world */
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 72px",
|
||||||
|
"font-weight: bold",
|
||||||
|
"-webkit-text-stroke: 2px black",
|
||||||
|
"color: #18BC9C"
|
||||||
|
].join(";");
|
||||||
|
clog("%cHey, hold on!", css);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 26px",
|
||||||
|
"font-weight: bold"
|
||||||
|
].join(";");
|
||||||
|
|
||||||
|
const css_2 = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 26px",
|
||||||
|
"font-weight: bold",
|
||||||
|
"color: blue"
|
||||||
|
].join(";");
|
||||||
|
|
||||||
|
const display_detect = /./;
|
||||||
|
display_detect.toString = function() { print_security(); return ""; };
|
||||||
|
|
||||||
|
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
|
||||||
|
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
||||||
|
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
|
||||||
|
clog("%c ", display_detect);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +1,14 @@
|
||||||
import "core-js/stable";
|
|
||||||
import "./polifill";
|
|
||||||
import "./css";
|
|
||||||
|
|
||||||
import * as loader from "./loader/loader";
|
|
||||||
import {ApplicationLoader} from "./loader/loader";
|
|
||||||
import {getUrlParameter} from "./loader/utils";
|
|
||||||
|
|
||||||
if(window["loader"]) {
|
if(window["loader"]) {
|
||||||
throw "an loader instance has already been defined";
|
throw "an loader instance has already been defined";
|
||||||
}
|
}
|
||||||
window["loader"] = loader;
|
|
||||||
/* let the loader register himself at the window first */
|
|
||||||
|
|
||||||
const target = getUrlParameter("loader-target") || "app";
|
export * from "./loader/loader";
|
||||||
console.info("Loading app with loader \"%s\"", target);
|
export * as loaderAnimation from "./animation";
|
||||||
|
|
||||||
let appLoader: ApplicationLoader;
|
import "./bootstrap";
|
||||||
if(target === "empty") {
|
|
||||||
appLoader = new (require("./targets/empty").default);
|
/* FIXME: This is glue! */
|
||||||
} else if(target === "manifest") {
|
if(window["loader"]) {
|
||||||
appLoader = new (require("./targets/maifest-target").default);
|
throw "an loader instance has already been defined";
|
||||||
} else {
|
|
||||||
appLoader = new (require("./targets/app").default);
|
|
||||||
}
|
|
||||||
setTimeout(() => appLoader.execute(), 0);
|
|
||||||
|
|
||||||
export {};
|
|
||||||
|
|
||||||
if(__build.target === "client") {
|
|
||||||
/* do this so we don't get a react dev tools warning within the client */
|
|
||||||
if(!('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window))
|
|
||||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {};
|
|
||||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {};
|
|
||||||
}
|
}
|
||||||
|
window["loader"] = module.exports;
|
|
@ -1,11 +1,12 @@
|
||||||
import {config, critical_error, SourcePath} from "./loader";
|
import {config, critical_error, SourcePath} from "./loader";
|
||||||
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
import {executeParallelLoad, LoadCallback, LoadSyntaxError, ParallelOptions} from "./Utils";
|
||||||
|
|
||||||
let _script_promises: {[key: string]: Promise<void>} = {};
|
let _script_promises: {[key: string]: Promise<void>} = {};
|
||||||
|
|
||||||
function load_script_url(url: string) : Promise<void> {
|
function load_script_url(url: string) : Promise<void> {
|
||||||
if(typeof _script_promises[url] === "object")
|
if(typeof _script_promises[url] === "object") {
|
||||||
return _script_promises[url];
|
return _script_promises[url];
|
||||||
|
}
|
||||||
|
|
||||||
return (_script_promises[url] = new Promise((resolve, reject) => {
|
return (_script_promises[url] = new Promise((resolve, reject) => {
|
||||||
const script_tag: HTMLScriptElement = document.createElement("script");
|
const script_tag: HTMLScriptElement = document.createElement("script");
|
||||||
|
@ -67,40 +68,18 @@ export interface Options {
|
||||||
cache_tag?: string;
|
cache_tag?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function load(path: SourcePath, options: Options) : Promise<void> {
|
export async function loadScript(path: SourcePath, options: Options) : Promise<void> {
|
||||||
if(Array.isArray(path)) { //We have fallback scripts
|
await load_script_url(path + (options.cache_tag || ""));
|
||||||
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;
|
type MultipleOptions = Options | ParallelOptions;
|
||||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
|
export async function loadScripts(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
|
||||||
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
|
const result = await executeParallelLoad<SourcePath>(paths, e => loadScript(e, options), e => e, options, callback);
|
||||||
if(result.failed.length > 0) {
|
if(result.failed.length > 0) {
|
||||||
if(config.error) {
|
if(config.error) {
|
||||||
console.error("Failed to load the following scripts:");
|
console.error("Failed to load the following scripts:");
|
||||||
for(const script of result.failed) {
|
for(const script of result.failed) {
|
||||||
const sname = script_name(script.request, false);
|
const sname = script.request;
|
||||||
if(script.error instanceof LoadSyntaxError) {
|
if(script.error instanceof LoadSyntaxError) {
|
||||||
const source = script.error.source as Error;
|
const source = script.error.source as Error;
|
||||||
if(source.name === "TypeError") {
|
if(source.name === "TypeError") {
|
||||||
|
@ -126,11 +105,11 @@ export async function load_multiple(paths: SourcePath[], options: MultipleOption
|
||||||
} else if(typeof error === "string") {
|
} else if(typeof error === "string") {
|
||||||
errorMessage = error;
|
errorMessage = error;
|
||||||
} else {
|
} else {
|
||||||
console.error("Script %s loading error: %o", script_name(result.failed[0].request, false), error);
|
console.error("Script %s loading error: %o", result.failed[0].request, error);
|
||||||
errorMessage = "View the browser console for more information!";
|
errorMessage = "View the browser console for more information!";
|
||||||
}
|
}
|
||||||
critical_error("Failed to load script " + script_name(result.failed[0].request, true), errorMessage);
|
critical_error("Failed to load script " + result.failed[0].request, errorMessage);
|
||||||
}
|
}
|
||||||
throw "failed to load script " + script_name(result.failed[0].request, false) + " (" + errorMessage + ")";
|
throw "failed to load script " + result.failed[0].request + " (" + errorMessage + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,18 +1,22 @@
|
||||||
import {config, critical_error, SourcePath} from "./loader";
|
import {config, critical_error, SourcePath} from "./loader";
|
||||||
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
import {executeParallelLoad, LoadCallback, LoadSyntaxError, ParallelOptions} from "./Utils";
|
||||||
|
|
||||||
let _style_promises: {[key: string]: Promise<void>} = {};
|
let loadedStyles: {[key: string]: Promise<void>} = {};
|
||||||
|
|
||||||
function load_style_url(url: string) : Promise<void> {
|
function load_style_url(url: string) : Promise<void> {
|
||||||
if(typeof _style_promises[url] === "object")
|
if(typeof loadedStyles[url] === "object") {
|
||||||
return _style_promises[url];
|
return loadedStyles[url];
|
||||||
|
}
|
||||||
|
|
||||||
return (_style_promises[url] = new Promise((resolve, reject) => {
|
return (loadedStyles[url] = new Promise((resolve, reject) => {
|
||||||
const tag: HTMLLinkElement = document.createElement("link");
|
const tag: HTMLLinkElement = document.createElement("link");
|
||||||
|
|
||||||
let error = false;
|
let error = false;
|
||||||
const error_handler = (event: ErrorEvent) => {
|
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(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!
|
if(event.filename == tag.href) { //FIXME!
|
||||||
window.removeEventListener('error', error_handler as any);
|
window.removeEventListener('error', error_handler as any);
|
||||||
|
|
||||||
|
@ -42,8 +46,9 @@ function load_style_url(url: string) : Promise<void> {
|
||||||
tag.onerror = error => {
|
tag.onerror = error => {
|
||||||
cleanup();
|
cleanup();
|
||||||
tag.remove();
|
tag.remove();
|
||||||
if(config.error)
|
if(config.error) {
|
||||||
console.error("File load error for file %s: %o", url, error);
|
console.error("File load error for file %s: %o", url, error);
|
||||||
|
}
|
||||||
reject("failed to load file " + url);
|
reject("failed to load file " + url);
|
||||||
};
|
};
|
||||||
tag.onload = () => {
|
tag.onload = () => {
|
||||||
|
@ -80,14 +85,14 @@ function load_style_url(url: string) : Promise<void> {
|
||||||
|
|
||||||
document.getElementById("style").appendChild(tag);
|
document.getElementById("style").appendChild(tag);
|
||||||
tag.href = config.baseUrl + url;
|
tag.href = config.baseUrl + url;
|
||||||
})).then(result => {
|
})).then(() => {
|
||||||
/* cleanup memory */
|
/* cleanup memory */
|
||||||
_style_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
|
loadedStyles[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
|
||||||
return _style_promises[url];
|
return loadedStyles[url];
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
/* cleanup memory */
|
/* cleanup memory */
|
||||||
_style_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
|
loadedStyles[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
|
||||||
return _style_promises[url];
|
return loadedStyles[url];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,39 +100,18 @@ export interface Options {
|
||||||
cache_tag?: string;
|
cache_tag?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function load(path: SourcePath, options: Options) : Promise<void> {
|
export async function loadStyle(path: SourcePath, options: Options) : Promise<void> {
|
||||||
if(Array.isArray(path)) { //We have fallback scripts
|
await load_style_url(path + (options.cache_tag || ""));
|
||||||
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 type MultipleOptions = Options | ParallelOptions;
|
||||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
|
export async function loadStyles(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
|
||||||
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
|
const result = await executeParallelLoad<SourcePath>(paths, e => loadStyle(e, options), e => e, options, callback);
|
||||||
if(result.failed.length > 0) {
|
if(result.failed.length > 0) {
|
||||||
if(config.error) {
|
if(config.error) {
|
||||||
console.error("Failed to load the following style sheets:");
|
console.error("Failed to load the following style sheets:");
|
||||||
for(const style of result.failed) {
|
for(const style of result.failed) {
|
||||||
const sname = script_name(style.request, false);
|
const sname = style.request;
|
||||||
if(style.error instanceof LoadSyntaxError) {
|
if(style.error instanceof LoadSyntaxError) {
|
||||||
console.log(" - %s: %o", sname, style.error.source);
|
console.log(" - %s: %o", sname, style.error.source);
|
||||||
} else {
|
} else {
|
||||||
|
@ -136,7 +120,7 @@ export async function load_multiple(paths: SourcePath[], options: MultipleOption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
critical_error("Failed to load style " + script_name(result.failed[0].request, true) + " <br>" + "View the browser console for more information!");
|
critical_error("Failed to load style <code>" + result.failed[0].request + "</code><br>" + "View the browser console for more information!");
|
||||||
throw "failed to load style " + script_name(result.failed[0].request, false);
|
throw "failed to load style " + result.failed[0].request;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import {SourcePath} from "./loader";
|
import {Options} from "./ScriptLoader";
|
||||||
import {Options} from "./script_loader";
|
|
||||||
|
|
||||||
export const getUrlParameter = key => {
|
export const getUrlParameter = key => {
|
||||||
const match = location.search.match(new RegExp("(.*[?&]|^)" + key + "=([^&]+)($|&.*)"));
|
const match = location.search.match(new RegExp("(.*[?&]|^)" + key + "=([^&]+)($|&.*)"));
|
||||||
if(!match)
|
if(!match) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return match[2];
|
return match[2];
|
||||||
};
|
};
|
||||||
|
@ -16,24 +16,15 @@ export class LoadSyntaxError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function script_name(path: SourcePath, html: boolean) {
|
|
||||||
if(Array.isArray(path)) {
|
|
||||||
return path.filter(e => !!e).map(e => script_name(e, html)).join(" or ");
|
|
||||||
} else if(typeof(path) === "string")
|
|
||||||
return html ? "<code>" + path + "</code>" : path;
|
|
||||||
else
|
|
||||||
return html ? "<code>" + path.url + "</code>" : path.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParallelOptions extends Options {
|
export interface ParallelOptions extends Options {
|
||||||
max_parallel_requests?: number
|
maxParallelRequests?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParallelResult<T> {
|
export interface ParallelResult<T> {
|
||||||
succeeded: T[];
|
succeeded: T[];
|
||||||
failed: {
|
failed: {
|
||||||
request: T,
|
request: T,
|
||||||
error: T
|
error: any
|
||||||
}[],
|
}[],
|
||||||
|
|
||||||
skipped: T[];
|
skipped: T[];
|
||||||
|
@ -41,15 +32,22 @@ export interface ParallelResult<T> {
|
||||||
|
|
||||||
export type LoadCallback<T> = (entry: T, state: "loading" | "loaded") => void;
|
export type LoadCallback<T> = (entry: T, state: "loading" | "loaded") => void;
|
||||||
|
|
||||||
export async function load_parallel<T>(requests: T[], executor: (_: T) => Promise<void>, stringify: (_: T) => string, options: ParallelOptions, callback?: LoadCallback<T>) : Promise<ParallelResult<T>> {
|
export async function executeParallelLoad<T>(
|
||||||
|
requests: T[],
|
||||||
|
executor: (_: T) => Promise<void>,
|
||||||
|
stringify: (_: T) => string,
|
||||||
|
options: ParallelOptions,
|
||||||
|
callback?: LoadCallback<T>
|
||||||
|
) : Promise<ParallelResult<T>> {
|
||||||
const result: ParallelResult<T> = { failed: [], succeeded: [], skipped: [] };
|
const result: ParallelResult<T> = { failed: [], succeeded: [], skipped: [] };
|
||||||
const pendingRequests = requests.slice(0).reverse(); /* we're only able to pop from the back */
|
const pendingRequests = requests.slice(0).reverse(); /* we're only able to pop from the back */
|
||||||
const currentRequests = {};
|
const currentRequests = {};
|
||||||
|
|
||||||
if(typeof callback === "undefined")
|
if(typeof callback === "undefined") {
|
||||||
callback = () => {};
|
callback = () => {};
|
||||||
|
}
|
||||||
|
|
||||||
const maxParallelRequests = typeof options.max_parallel_requests === "number" && options.max_parallel_requests > 0 ? options.max_parallel_requests : Number.MAX_SAFE_INTEGER;
|
const maxParallelRequests = typeof options.maxParallelRequests === "number" && options.maxParallelRequests > 0 ? options.maxParallelRequests : Number.MAX_SAFE_INTEGER;
|
||||||
while (pendingRequests.length > 0) {
|
while (pendingRequests.length > 0) {
|
||||||
while(Object.keys(currentRequests).length < maxParallelRequests) {
|
while(Object.keys(currentRequests).length < maxParallelRequests) {
|
||||||
const element = pendingRequests.pop();
|
const element = pendingRequests.pop();
|
||||||
|
@ -60,18 +58,21 @@ export async function load_parallel<T>(requests: T[], executor: (_: T) => Promis
|
||||||
delete currentRequests[name];
|
delete currentRequests[name];
|
||||||
callback(element, "loaded");
|
callback(element, "loaded");
|
||||||
});
|
});
|
||||||
if(pendingRequests.length == 0)
|
|
||||||
|
if(pendingRequests.length == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait 'till a new "slot" for downloading is free.
|
* Wait 'till a new "slot" for downloading is free.
|
||||||
* This should also not throw because any errors will be caught before.
|
* This should also not throw because any errors will be caught before.
|
||||||
*/
|
*/
|
||||||
await Promise.race(Object.keys(currentRequests).map(e => currentRequests[e]));
|
await Promise.race(Object.keys(currentRequests).map(e => currentRequests[e]));
|
||||||
if(result.failed.length > 0)
|
if(result.failed.length > 0) {
|
||||||
break; /* finish loading the other requests and than show the error */
|
break; /* finish loading the other requests and than show the error */
|
||||||
}
|
}
|
||||||
|
}
|
||||||
await Promise.all(Object.keys(currentRequests).map(e => currentRequests[e]));
|
await Promise.all(Object.keys(currentRequests).map(e => currentRequests[e]));
|
||||||
result.skipped.push(...pendingRequests);
|
result.skipped.push(...pendingRequests);
|
||||||
return result;
|
return result;
|
|
@ -1,7 +1,5 @@
|
||||||
import * as script_loader from "./script_loader";
|
|
||||||
import * as style_loader from "./style_loader";
|
|
||||||
import * as Animation from "../animation";
|
import * as Animation from "../animation";
|
||||||
import {getUrlParameter} from "./utils";
|
import {getUrlParameter} from "./Utils";
|
||||||
|
|
||||||
export interface ApplicationLoader {
|
export interface ApplicationLoader {
|
||||||
execute();
|
execute();
|
||||||
|
@ -76,30 +74,9 @@ export enum Stage {
|
||||||
DONE
|
DONE
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache_tag: string | undefined;
|
|
||||||
let currentStage: Stage = undefined;
|
let currentStage: Stage = undefined;
|
||||||
const tasks: {[key:number]: InternalTask[]} = {};
|
const tasks: {[key:number]: InternalTask[]} = {};
|
||||||
|
|
||||||
/* test if all files shall be load from cache or fetch again */
|
|
||||||
function loader_cache_tag() {
|
|
||||||
if(__build.mode === "debug") {
|
|
||||||
cache_tag = "?_ts=" + Date.now();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cached_version = localStorage.getItem("cached_version");
|
|
||||||
if(!cached_version || cached_version !== __build.version) {
|
|
||||||
register_task(Stage.LOADED, {
|
|
||||||
priority: 0,
|
|
||||||
name: "cached version updater",
|
|
||||||
function: async () => {
|
|
||||||
localStorage.setItem("cached_version", __build.version);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cache_tag = "?_version=" + __build.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ModuleMapping = {
|
export type ModuleMapping = {
|
||||||
application: string,
|
application: string,
|
||||||
modules: {
|
modules: {
|
||||||
|
@ -174,22 +151,23 @@ export function setCurrentTaskName(taskId: number, name: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function execute(customLoadingAnimations: boolean) {
|
export async function execute(customLoadingAnimations: boolean) {
|
||||||
if(!await Animation.initialize(customLoadingAnimations))
|
if(!await Animation.initialize(customLoadingAnimations)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loader_cache_tag();
|
const timestampBegin = Date.now();
|
||||||
|
|
||||||
const load_begin = Date.now();
|
|
||||||
|
|
||||||
let begin: number = 0;
|
let begin: number = 0;
|
||||||
let end: number = Date.now();
|
let end: number = Date.now();
|
||||||
while(currentStage <= Stage.LOADED || typeof(currentStage) === "undefined") {
|
while(currentStage <= Stage.LOADED || typeof currentStage === "undefined") {
|
||||||
|
|
||||||
let pendingTasks: InternalTask[] = [];
|
let pendingTasks: InternalTask[] = [];
|
||||||
while((tasks[currentStage] || []).length > 0) {
|
while((tasks[currentStage] || []).length > 0) {
|
||||||
if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) {
|
if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) {
|
||||||
pendingTasks.push(tasks[currentStage].pop());
|
pendingTasks.push(tasks[currentStage].pop());
|
||||||
} else break;
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors: {
|
const errors: {
|
||||||
|
@ -282,7 +260,7 @@ export async function execute(customLoadingAnimations: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config.verbose)
|
if(config.verbose)
|
||||||
console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin);
|
console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - timestampBegin);
|
||||||
|
|
||||||
Animation.finalize(config.abortAnimationOnFinish);
|
Animation.finalize(config.abortAnimationOnFinish);
|
||||||
}
|
}
|
||||||
|
@ -351,79 +329,4 @@ export function critical_error_handler(handler?: ErrorHandler, override?: boolea
|
||||||
}
|
}
|
||||||
|
|
||||||
/* loaders */
|
/* loaders */
|
||||||
export type DependSource = {
|
export type SourcePath = string;
|
||||||
url: string;
|
|
||||||
depends: string[];
|
|
||||||
}
|
|
||||||
export type SourcePath = string | DependSource | string[];
|
|
||||||
|
|
||||||
export const scripts = script_loader;
|
|
||||||
export const style = style_loader;
|
|
||||||
|
|
||||||
/* Hello World message */
|
|
||||||
{
|
|
||||||
const clog = console.log;
|
|
||||||
const print_security = () => {
|
|
||||||
{
|
|
||||||
const css = [
|
|
||||||
"display: block",
|
|
||||||
"text-align: center",
|
|
||||||
"font-size: 42px",
|
|
||||||
"font-weight: bold",
|
|
||||||
"-webkit-text-stroke: 2px black",
|
|
||||||
"color: red"
|
|
||||||
].join(";");
|
|
||||||
clog("%c ", "font-size: 100px;");
|
|
||||||
clog("%cSecurity warning:", css);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const css = [
|
|
||||||
"display: block",
|
|
||||||
"text-align: center",
|
|
||||||
"font-size: 18px",
|
|
||||||
"font-weight: bold"
|
|
||||||
].join(";");
|
|
||||||
|
|
||||||
clog("%cPasting anything in here could give attackers access to your data.", css);
|
|
||||||
clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css);
|
|
||||||
clog("%c ", "font-size: 100px;");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* print the hello world */
|
|
||||||
{
|
|
||||||
const css = [
|
|
||||||
"display: block",
|
|
||||||
"text-align: center",
|
|
||||||
"font-size: 72px",
|
|
||||||
"font-weight: bold",
|
|
||||||
"-webkit-text-stroke: 2px black",
|
|
||||||
"color: #18BC9C"
|
|
||||||
].join(";");
|
|
||||||
clog("%cHey, hold on!", css);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const css = [
|
|
||||||
"display: block",
|
|
||||||
"text-align: center",
|
|
||||||
"font-size: 26px",
|
|
||||||
"font-weight: bold"
|
|
||||||
].join(";");
|
|
||||||
|
|
||||||
const css_2 = [
|
|
||||||
"display: block",
|
|
||||||
"text-align: center",
|
|
||||||
"font-size: 26px",
|
|
||||||
"font-weight: bold",
|
|
||||||
"color: blue"
|
|
||||||
].join(";");
|
|
||||||
|
|
||||||
const display_detect = /./;
|
|
||||||
display_detect.toString = function() { print_security(); return ""; };
|
|
||||||
|
|
||||||
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
|
|
||||||
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
|
||||||
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
|
|
||||||
clog("%c ", display_detect);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
import * as loader from "./loader/loader";
|
import * as loader from "./loader/loader";
|
||||||
import {config} from "./loader/loader";
|
import {config} from "./loader/loader";
|
||||||
import {script_name} from "./loader/utils";
|
import {loadStyles} from "./loader/StyleLoader";
|
||||||
|
import {loadScripts} from "./loader/ScriptLoader";
|
||||||
|
|
||||||
export interface TeaManifest {
|
export interface ApplicationManifest {
|
||||||
version: number;
|
version: number;
|
||||||
|
|
||||||
chunks: {
|
chunks: {
|
||||||
|
@ -24,15 +25,17 @@ export interface TeaManifest {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let manifest: TeaManifest;
|
let manifest: ApplicationManifest;
|
||||||
export async function loadManifest() : Promise<TeaManifest> {
|
export async function loadManifest() : Promise<ApplicationManifest> {
|
||||||
if(manifest) {
|
if(manifest) {
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(config.baseUrl + "/manifest.json?_date=" + Date.now());
|
const response = await fetch(config.baseUrl + "/manifest.json?_date=" + Date.now());
|
||||||
if(!response.ok) throw response.status + " " + response.statusText;
|
if(!response.ok) {
|
||||||
|
throw response.status + " " + response.statusText;
|
||||||
|
}
|
||||||
|
|
||||||
manifest = await response.json();
|
manifest = await response.json();
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
|
@ -40,8 +43,10 @@ export async function loadManifest() : Promise<TeaManifest> {
|
||||||
loader.critical_error("Failed to load manifest.json", error);
|
loader.critical_error("Failed to load manifest.json", error);
|
||||||
throw "failed to load manifest.json";
|
throw "failed to load manifest.json";
|
||||||
}
|
}
|
||||||
if(manifest.version !== 2)
|
|
||||||
|
if(manifest.version !== 2) {
|
||||||
throw "invalid manifest version";
|
throw "invalid manifest version";
|
||||||
|
}
|
||||||
|
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
@ -51,30 +56,31 @@ export async function loadManifestTarget(chunkName: string, taskId: number) {
|
||||||
loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunkName + " is missing.");
|
loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunkName + " is missing.");
|
||||||
throw "missing entry chunk";
|
throw "missing entry chunk";
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.module_mapping().push({
|
loader.module_mapping().push({
|
||||||
application: chunkName,
|
application: chunkName,
|
||||||
modules: manifest.chunks[chunkName].modules
|
modules: manifest.chunks[chunkName].modules
|
||||||
});
|
});
|
||||||
|
|
||||||
loader.style.load_multiple(manifest.chunks[chunkName].css_files.map(e => e.file), {
|
await loadStyles(manifest.chunks[chunkName].css_files.map(e => e.file), {
|
||||||
cache_tag: undefined,
|
cache_tag: undefined,
|
||||||
max_parallel_requests: 4
|
maxParallelRequests: 4
|
||||||
}, (entry, state) => {
|
}, (entry, state) => {
|
||||||
if (state !== "loading") {
|
if (state !== "loading") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.setCurrentTaskName(taskId, script_name(entry, false));
|
loader.setCurrentTaskName(taskId, entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
await loader.scripts.load_multiple(manifest.chunks[chunkName].files.map(e => e.file), {
|
await loadScripts(manifest.chunks[chunkName].files.map(e => e.file), {
|
||||||
cache_tag: undefined,
|
cache_tag: undefined,
|
||||||
max_parallel_requests: 4
|
maxParallelRequests: 4
|
||||||
}, (script, state) => {
|
}, (script, state) => {
|
||||||
if(state !== "loading") {
|
if(state !== "loading") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.setCurrentTaskName(taskId, script_name(script, false));
|
loader.setCurrentTaskName(taskId, script);
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -3,21 +3,13 @@ import * as loader from "../loader/loader";
|
||||||
import {ApplicationLoader} from "../loader/loader";
|
import {ApplicationLoader} from "../loader/loader";
|
||||||
import {loadManifest, loadManifestTarget} from "../maifest";
|
import {loadManifest, loadManifestTarget} from "../maifest";
|
||||||
|
|
||||||
/* all javascript loaders */
|
|
||||||
const loader_javascript = {
|
|
||||||
load_scripts: async taskId => {
|
|
||||||
loader.setCurrentTaskName(taskId, "manifest");
|
|
||||||
await loadManifest();
|
|
||||||
await loadManifestTarget(__build.entry_chunk_name, taskId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
name: "secure tester",
|
name: "secure tester",
|
||||||
function: async () => {
|
function: async () => {
|
||||||
/* we need https or localhost to use some things like the storage API */
|
/* we need https or localhost to use some things like the storage API */
|
||||||
if(typeof isSecureContext === "undefined")
|
if(typeof isSecureContext === "undefined") {
|
||||||
(window as any)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
|
(window as any)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
|
||||||
|
}
|
||||||
|
|
||||||
if(!isSecureContext) {
|
if(!isSecureContext) {
|
||||||
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
|
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
|
||||||
|
@ -28,8 +20,12 @@ loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
});
|
});
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||||
name: "scripts",
|
name: "manifest",
|
||||||
function: loader_javascript.load_scripts,
|
function: async taskId => {
|
||||||
|
loader.setCurrentTaskName(taskId, "manifest");
|
||||||
|
await loadManifest();
|
||||||
|
await loadManifestTarget(__build.entry_chunk_name, taskId);
|
||||||
|
},
|
||||||
priority: 10
|
priority: 10
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import "./shared";
|
||||||
import * as loader from "../loader/loader";
|
import * as loader from "../loader/loader";
|
||||||
import {ApplicationLoader, Stage} from "../loader/loader";
|
import {ApplicationLoader, Stage} from "../loader/loader";
|
||||||
import {loadManifest, loadManifestTarget} from "../maifest";
|
import {loadManifest, loadManifestTarget} from "../maifest";
|
||||||
import {getUrlParameter} from "../loader/utils";
|
import {getUrlParameter} from "../loader/Utils";
|
||||||
|
|
||||||
export default class implements ApplicationLoader {
|
export default class implements ApplicationLoader {
|
||||||
execute() {
|
execute() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as loader from "../loader/loader";
|
import * as loader from "../loader/loader";
|
||||||
import {Stage} from "../loader/loader";
|
import {Stage} from "../loader/loader";
|
||||||
import {BrowserInfo, detect as detectBrowser,} from "detect-browser";
|
import {detect as detectBrowser} from "detect-browser";
|
||||||
|
|
||||||
loader.register_task(Stage.SETUP, {
|
loader.register_task(Stage.SETUP, {
|
||||||
name: "app init",
|
name: "app init",
|
||||||
|
|
Loading…
Add table
Reference in a new issue