Improved client entry handling

canary
WolverinDEV 2020-09-30 22:51:04 +02:00
parent 30357018c4
commit 7a97a74cd5
10 changed files with 70 additions and 190 deletions

8
client/app/external.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
interface Window {
__native_client_init_hook: () => void;
__native_client_init_shared: (webpackRequire: any) => void;
}
declare const __webpack_require__;
declare const __teaclient_preview_notice: any;
declare const __teaclient_preview_error: any;

View File

@ -1,7 +1,4 @@
declare const __webpack_require__;
window["shared-require"] = __webpack_require__;
window.__native_client_init_shared(__webpack_require__);
import "./index.scss";
/* firstly assign the shared-require */
setTimeout(() => require("tc-shared/main"), 0);
import "tc-shared/main";

View File

@ -42,6 +42,8 @@ export type Task = {
function: (taskId?: number) => Promise<void>
};
type InternalTask = Task & { initiator: string }
export enum Stage {
/*
loading loader required files (incl this)
@ -88,7 +90,7 @@ export enum Stage {
let cache_tag: string | undefined;
let currentStage: Stage = undefined;
const tasks: {[key:number]:Task[]} = {};
const tasks: {[key:number]: InternalTask[]} = {};
/* test if all files shall be load from cache or fetch again */
function loader_cache_tag() {
@ -129,18 +131,23 @@ export function finished() {
export function running() { return typeof(currentStage) !== "undefined"; }
export function register_task(stage: Stage, task: Task) {
let callee = new Error().stack.split("\n")[2].replace(/^\s*at (Object\.\.\/)?/, "");
if(callee.match(/^.* \(([:\\/_\-+0-9a-zA-Z.]+):([0-9]+):([0-9]+)\)$/)) {
callee = callee.replace(/^.* \(([:\\/_\-+0-9a-zA-Z.]+):([0-9]+):([0-9]+)\)$/, "$1:$2:$3");
}
if(!task.function) {
debugger;
throw "tried to register a loader task without a function";
throw "tried to register a loader task without a function at " + callee;
}
if(currentStage > stage) {
if(config.error)
console.warn("Register loading task, but it had already been finished. Executing task anyways!");
if(config.error) {
console.warn("Register loading task, but it had already been finished. Executing task anyways! Registerer: %s", callee);
}
const promise = task.function();
if(!promise) {
console.error("Loading task %s hasn't returned a promise!", task.name);
console.error("Loading task %s (%s) hasn't returned a promise!", task.name, callee);
return;
}
promise.catch(error => {
@ -155,7 +162,9 @@ export function register_task(stage: Stage, task: Task) {
}
const task_array = tasks[stage] || [];
task_array.push(task);
task_array.push(Object.assign({
initiator: callee
}, task));
tasks[stage] = task_array.sort((a, b) => a.priority - b.priority);
}
@ -190,7 +199,7 @@ export async function execute() {
let end: number = Date.now();
while(currentStage <= Stage.LOADED || typeof(currentStage) === "undefined") {
let pendingTasks: Task[] = [];
let pendingTasks: InternalTask[] = [];
while((tasks[currentStage] || []).length > 0) {
if(pendingTasks.length == 0 || pendingTasks[0].priority == tasks[currentStage][0].priority) {
pendingTasks.push(tasks[currentStage].pop());
@ -199,7 +208,7 @@ export async function execute() {
const errors: {
error: any,
task: Task
task: InternalTask
}[] = [];
for(const task of pendingTasks) {
@ -210,8 +219,9 @@ export async function execute() {
} as RunningTask;
try {
if(config.verbose)
if(config.verbose) {
console.debug("Executing loader %s (%d)", task.name, task.priority);
}
runningTasks.push(rTask);
const promise = task.function(rTask.taskId);
@ -256,9 +266,11 @@ export async function execute() {
if(errors.length > 0) {
if(config.loader_groups)
console.groupEnd();
console.error("Failed to execute loader. The following tasks failed (%d):", errors.length);
for(const error of errors)
console.error(" - %s: %o", error.task.name, error.error);
for(const error of errors) {
console.error(" - %s (%s): %o", error.task.name, error.task.initiator, error.error);
}
throw "failed to process step " + Stage[currentStage];
}

View File

@ -10,23 +10,8 @@ declare global {
}
}
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") {
const version_node = document.getElementById("app_version");
if(!version_node) return undefined;
const version = version_node.hasAttribute("value") ? version_node.getAttribute("value") : undefined;
if(!version) return undefined;
return (_ui_version = version);
}
return _ui_version;
function getCacheTag() {
return "?_ts=" + (__build.mode === "debug" ? Date.now() : __build.timestamp);
}
const LoaderTaskCallback = taskId => (script: SourcePath, state) => {
@ -101,7 +86,7 @@ loader.register_task(loader.Stage.TEMPLATES, {
"templates/modal/musicmanage.html",
"templates/modal/newcomer.html",
], {
cache_tag: cache_tag(),
cache_tag: getCacheTag(),
max_parallel_requests: -1
}, LoaderTaskCallback(taskId));
},
@ -154,7 +139,6 @@ loader.register_task(loader.Stage.SETUP, {
loader.register_task(loader.Stage.SETUP, {
name: "TeaClient tester",
function: async () => {
//@ts-ignore
if(typeof __teaclient_preview_notice !== "undefined" && typeof __teaclient_preview_error !== "undefined") {
loader.critical_error("Why you're opening TeaWeb within the TeaSpeak client?!");
throw "we're already a TeaClient!";
@ -165,34 +149,6 @@ loader.register_task(loader.Stage.SETUP, {
export default class implements ApplicationLoader {
execute() {
/* TeaClient */
if(window.require) {
if(__build.target !== "client") {
loader.critical_error("App seems not to be compiled for the client.", "This app has been compiled for " + __build.target);
return;
}
window.native_client = true;
const path = __non_webpack_require__("path");
const remote = __non_webpack_require__('electron').remote;
const render_entry = path.join(remote.app.getAppPath(), "/modules/", "renderer");
const render = __non_webpack_require__(render_entry);
loader.register_task(loader.Stage.INITIALIZING, {
name: "teaclient initialize",
function: render.initialize,
priority: 40
});
} else {
if(__build.target !== "web") {
loader.critical_error("App seems not to be compiled for the web.", "This app has been compiled for " + __build.target);
return;
}
window.native_client = false;
}
loader.execute_managed();
}
}

View File

@ -1,97 +0,0 @@
import * as loader from "../loader/loader";
let is_debug = false;
/* all javascript loaders */
const loader_javascript = {
detect_type: async () => {
/* test if js/proto.js is available. If so we're in debug mode */
const request = new XMLHttpRequest();
request.open('GET', 'js/proto.js', true);
await new Promise((resolve, reject) => {
request.onreadystatechange = () => {
if (request.readyState === 4){
is_debug = request.status !== 404;
resolve();
}
};
request.onerror = () => {
reject("Failed to detect app type");
};
request.send();
});
},
load_scripts: async () => {
await loader.scripts.load_multiple(["vendor/jquery/jquery.min.js"], {});
await loader.scripts.load_multiple([
["dist/certificate-popup.js"],
], {});
}
};
const loader_style = {
load_style: async () => {
if(is_debug) {
await loader_style.load_style_debug();
} else {
await loader_style.load_style_release();
}
},
load_style_debug: async () => {
await loader.style.load_multiple([
"css/static/main.css",
], {});
},
load_style_release: async () => {
await loader.style.load_multiple([
"css/static/main.css",
], {});
}
};
loader.register_task(loader.Stage.INITIALIZING, {
name: "app type test",
function: loader_javascript.detect_type,
priority: 20
});
loader.register_task(loader.Stage.JAVASCRIPT, {
name: "javascript",
function: loader_javascript.load_scripts,
priority: 10
});
loader.register_task(loader.Stage.STYLE, {
name: "style",
function: loader_style.load_style,
priority: 10
});
/* register tasks */
loader.register_task(loader.Stage.INITIALIZING, {
name: "safari fix",
function: async () => {
/* safari remove "fix" */
if(Element.prototype.remove === undefined)
Object.defineProperty(Element.prototype, "remove", {
enumerable: false,
configurable: false,
writable: false,
value: function(){
this.parentElement.removeChild(this);
}
});
},
priority: 50
});
if(!loader.running()) {
/* we know that we want to load the app */
loader.execute_managed();
}
export = {};

View File

@ -5,8 +5,6 @@ import {loadManifest, loadManifestTarget} from "../maifest";
import {getUrlParameter} from "../loader/utils";
export default class implements ApplicationLoader {
execute() {
loader.register_task(Stage.SETUP, {
function: async taskId => {
@ -67,26 +65,6 @@ export default class implements ApplicationLoader {
priority: 10
});
if(__build.target === "client") {
loader.register_task(Stage.SETUP, {
name: "native setup",
function: async () => {
const path = __non_webpack_require__("path");
const remote = __non_webpack_require__('electron').remote;
const render_entry = path.join(remote.app.getAppPath(), "/modules/", "renderer-manifest", "index");
const render = __non_webpack_require__(render_entry);
loader.register_task(loader.Stage.SETUP, {
name: "teaclient setup",
function: async () => await render.initialize(getUrlParameter("chunk")),
priority: 40
});
},
priority: 50
});
}
loader.execute_managed();
}
}

View File

@ -1,9 +1,6 @@
import * as loader from "../loader/loader";
import {Stage} from "../loader/loader";
import {
BrowserInfo,
detect as detectBrowser,
} from "detect-browser";
import {BrowserInfo, detect as detectBrowser,} from "detect-browser";
declare global {
interface Window {
@ -12,6 +9,30 @@ declare global {
}
}
loader.register_task(Stage.SETUP, {
name: "app init",
function: async () => {
/* TeaClient */
if(window.require || window.__native_client_init_hook) {
if(__build.target !== "client") {
loader.critical_error("App seems not to be compiled for the client.", "This app has been compiled for " + __build.target);
return;
}
window.__native_client_init_hook();
window.native_client = true;
} else {
if(__build.target !== "web") {
loader.critical_error("App seems not to be compiled for the web.", "This app has been compiled for " + __build.target);
return;
}
window.native_client = false;
}
},
priority: 1000
});
if(__build.target === "web") {
loader.register_task(Stage.SETUP, {
name: "outdated browser checker",

View File

@ -15,6 +15,10 @@ import {setupJSRender} from "../../../ui/jsrender";
import "../../../file/RemoteAvatars";
import "../../../file/RemoteIcons";
if("__native_client_init_shared" in window) {
window.__native_client_init_shared(__webpack_require__);
}
let modalRenderer: ModalRenderer;
let modalInstance: AbstractModal;
let modalClass: new (events: RegistryMap, userData: any) => AbstractModal;

View File

@ -1,5 +1,3 @@
@import "../../../css/static/mixin";
.globalContainer {
display: flex;
flex-direction: column;
@ -140,7 +138,8 @@
cursor: pointer;
font-size: 22px;
@include user-select(none);
user-select: none;
-webkit-user-select: none;
/* Hide the browser's default checkbox */
input {

View File

@ -16,8 +16,10 @@ export = () => config_base.config("client").then(config => {
throw "invalid config";
config.externals.push((context, request, callback) => {
if (request.startsWith("tc-backend/"))
if (request.startsWith("tc-backend/")) {
return callback(null, `window["backend-loader"].require("${request}")`);
}
callback(undefined, undefined);
});