import { id as mid } from "module"; import { TraceSource } from "@implab/core-amd/log/TraceSource"; import { MapOf } from "@implab/core-amd/interfaces"; import { mixin } from "@implab/core-amd/safe"; const trace = TraceSource.get(mid); function on(node: HTMLElement, eventName: T, handler: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any): () => void { // Add an event listener to a DOM node node.addEventListener(eventName, handler, false); return () => node.removeEventListener(eventName, handler, false); } interface NodeLoadResult { node: HTMLElement; } class DomInject { injectionPoint?: HTMLElement; injectAfter?: HTMLElement; _map: MapOf> = {}; _inject(name: T, attr: Partial) { const node = document.createElement(name); return new Promise((ok, fail) => { const cleanup = () => { noerr(); noload(); }; const noload = on(node, "load", () => { ok({ node }); cleanup(); }); const noerr = on(node, "error", e => { fail({ error: e, node }); cleanup(); }); mixin(node, attr); const _injectionPoint = this.injectionPoint || document.head; const _injectBefore = this.injectAfter ? this.injectAfter.nextSibling : null; _injectionPoint.insertBefore(node, _injectBefore); }); } async injectScript(url: string) { let d = this._map[url]; if (!d) { trace.log("js {0}", url); d = this._inject("script", { type: "text/javascript", charset: "utf-8", src: url }); this._map[url] = d; } try { await d; trace.log("done {0}", url); } catch (e) { trace.error("failed {0}: {1}", url, e); throw e; } } async injectStylesheet(url: string) { let d = this._map[url]; if (!d) { trace.log("js {0}", url); d = this._inject("link", { type: "text/css", rel: "stylesheet", href: url }); this._map[url] = d; } try { await d; trace.log("done {0}", url); } catch (e) { trace.error("failed {0}: {1}", url, e); throw e; } } }; const instance = new DomInject(); export default instance;