import { Logger } from "../commons/utils/logger"; import { makeBundle } from "./script-info"; // Polyfills import "es6-promise/auto"; import "unfetch/polyfill"; /** * Object stored in LocalStorage. * * @interface ScriptCache */ interface ScriptCache { version: number; script: string; } /** * Main loader class. * * @export * @class Loader */ export class Loader { /** * Run the loader. * * Loads polyfill if needed and bundle. * * @param {boolean} [autorun=false] * @return {Promise} * @memberof Loader */ public async run(autorun = false): Promise { try { if (autorun && window._RecSys.Bundle.Configuration.loader.autorun) { return; } const bundle = makeBundle(window._RecSys.Bundle.Properties, window._RecSys.Bundle.Configuration); return this.load(bundle.key, bundle.url, bundle.versionUrl); } catch (err) { Logger.log(err); } } /** * Script loading. * * @private * @param {string} cacheKey LocalStorage key to save script in. * @param {string} scriptUrl Script's URL. * @param {string} versionUrl Version's URL. * @memberof Loader */ private async load(cacheKey: string, scriptUrl: string, versionUrl: string): Promise { const value = localStorage.getItem(cacheKey); const cachedScript = (value ? JSON.parse(value) : null) as ScriptCache; const response = await fetch(versionUrl); const version = response.ok ? parseInt(await response.text(), 10) || 0 : 0; if (!cachedScript || cachedScript.version < version) { return this.updateScript(scriptUrl, cacheKey, version); } else { return this.appendScript(cachedScript.script, cacheKey); } } /** * Update script using url; * * @private * @param {string} scriptUrl Script's url. * @param {string} cacheKey Local Storage key to store the script in. * @param {number} version Script version. * @memberof Loader */ private async updateScript(scriptUrl: string, cacheKey: string, version: number): Promise { const response = await fetch(scriptUrl); if (response.ok) { const script = await response.text(); const cacheValue = JSON.stringify({ script, version }); localStorage.setItem(cacheKey, cacheValue); this.appendScript(script, cacheKey); } } /** * Append script to document body. * * @private * @param {string} scriptAsText Script as text. * @param {string} name Goes in data-biggy-script attribute. * @memberof Loader */ private appendScript(scriptAsText: string, name: string) { const scriptElement = document.createElement("script"); scriptElement.setAttribute("data-biggy-script", name); scriptElement.text = scriptAsText; const elements = Array.from(document.getElementsByTagName("head")) as HTMLElement[]; const headElement = elements != null ? elements[0] : null; if (headElement != null) { let duplicate = false; for (let i = 0; i < headElement.children.length && !duplicate; i++) { const child = headElement.children.item(i); if (child != null) { duplicate = child.getAttribute("data-biggy-script") === name; } } if (!duplicate) { headElement.appendChild(scriptElement); } } } }