diff --git a/src/js/lib/content/src/index.ts b/src/js/lib/content/src/index.ts index bc583476ff..3d832316d7 100644 --- a/src/js/lib/content/src/index.ts +++ b/src/js/lib/content/src/index.ts @@ -2,11 +2,16 @@ /// user with a visual representation of this process (welcome screen). It also implements a view /// allowing to choose a debug rendering test from. +// @ts-ignore import * as loader_module from 'enso-studio-common/src/loader' +// @ts-ignore import * as html_utils from 'enso-studio-common/src/html_utils' -import * as animation from 'enso-studio-common/src/animation' +// @ts-ignore import * as globalConfig from '../../../../config.yaml' +// @ts-ignore import cfg from '../../../config' +// @ts-ignore +import assert from 'assert' // ================= // === Constants === @@ -18,7 +23,26 @@ const ALIVE_LOG_INTERVAL = 1000 * 60 // === Global API === // ================== -let API = {} +class ContentApi { + main: (inputConfig: any) => Promise + private logger: MixpanelLogger + + initLogging(config: Config) { + assert(typeof config.no_data_gathering == 'boolean') + if (!config.no_data_gathering) { + this.logger = new MixpanelLogger() + } + } + remoteLog(event: string, data?: any) { + if (this.logger) { + this.logger.log(event, data) + } + } +} + +const API = new ContentApi() + +// @ts-ignore window[globalConfig.windowAppScopeName] = API // ======================== @@ -30,24 +54,26 @@ let incorrect_mime_type_warning = ` 'application/wasm' MIME type. Falling back to 'WebAssembly.instantiate' which is slower. ` -function wasm_instantiate_streaming(resource, imports) { - return WebAssembly.instantiateStreaming(resource, imports).catch(e => { - return wasm_fetch - .then(r => { - if (r.headers.get('Content-Type') != 'application/wasm') { - console.warn(`${incorrect_mime_type_warning} Original error:\n`, e) - return r.arrayBuffer() - } else { - throw "Server not configured to serve WASM with 'application/wasm' mime type." - } - }) - .then(bytes => WebAssembly.instantiate(bytes, imports)) - }) +async function wasm_instantiate_streaming( + resource: Response, + imports: WebAssembly.Imports +): Promise { + try { + return WebAssembly.instantiateStreaming(resource, imports) + } catch (e) { + const r = await resource + if (r.headers.get('Content-Type') != 'application/wasm') { + console.warn(`${incorrect_mime_type_warning} Original error:\n`, e) + return r.arrayBuffer() + } else { + throw "Server not configured to serve WASM with 'application/wasm' mime type." + } + } } /// Downloads the WASM binary and its dependencies. Displays loading progress bar unless provided /// with `{use_loader:false}` option. -async function download_content(config) { +async function download_content(config: { wasm_glue_url: RequestInfo; wasm_url: RequestInfo }) { let wasm_glue_fetch = await fetch(config.wasm_glue_url) let wasm_fetch = await fetch(config.wasm_url) let loader = new loader_module.Loader([wasm_glue_fetch, wasm_fetch], config) @@ -74,10 +100,12 @@ async function download_content(config) { console.log('WASM dependencies loaded.') console.log('Starting online WASM compilation.') let wasm_loader = await wasm_instantiate_streaming(wasm_fetch, imports) + // @ts-ignore wasm_loader.wasm_glue = wasm_glue return wasm_loader }) + // @ts-ignore let wasm = await wasm_loader.then(({ instance, module, wasm_glue }) => { let wasm = instance.exports wasm_glue.after_load(wasm, module) @@ -100,7 +128,7 @@ let main_entry_point = 'ide' let wasm_entry_point_pfx = 'entry_point_' /// Displays a debug screen which allows the user to run one of predefined debug examples. -function show_debug_screen(wasm, msg) { +function show_debug_screen(wasm: any, msg: string) { API.remoteLog('show_debug_screen') let names = [] for (let fn of Object.getOwnPropertyNames(wasm)) { @@ -116,7 +144,6 @@ function show_debug_screen(wasm, msg) { let debug_screen_div = html_utils.new_top_level_div() let newDiv = document.createElement('div') let newContent = document.createTextNode(msg + 'Available entry points:') - let currentDiv = document.getElementById('app') let ul = document.createElement('ul') debug_screen_div.style.position = 'absolute' debug_screen_div.style.zIndex = 1 @@ -171,6 +198,8 @@ function printScamWarning() { // ====================== class MixpanelLogger { + private readonly mixpanel: any + constructor() { this.mixpanel = require('mixpanel-browser') this.mixpanel.init( @@ -180,7 +209,7 @@ class MixpanelLogger { ) } - log(event, data) { + log(event: string, data: any) { if (this.mixpanel) { event = MixpanelLogger.trim_message(event) if (data !== undefined && data !== null) { @@ -194,7 +223,7 @@ class MixpanelLogger { } } - static trim_message(message) { + static trim_message(message: string) { const MAX_MESSAGE_LENGTH = 500 let trimmed = message.substr(0, MAX_MESSAGE_LENGTH) if (trimmed.length < message.length) { @@ -211,13 +240,20 @@ class MixpanelLogger { const logsFns = ['log', 'info', 'debug', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd'] class LogRouter { + private buffer: any[] + private readonly raw: {} + autoFlush: boolean + constructor() { this.buffer = [] this.raw = {} this.autoFlush = true + // @ts-ignore console.autoFlush = true for (let name of logsFns) { + // @ts-ignore this.raw[name] = console[name] + // @ts-ignore console[name] = (...args) => { this.handle(name, args) } @@ -226,15 +262,18 @@ class LogRouter { auto_flush_on() { this.autoFlush = true + // @ts-ignore console.autoFlush = true for (let { name, args } of this.buffer) { + // @ts-ignore this.raw[name](...args) } this.buffer = [] } - handle(name, args) { + handle(name: string, args: any[]) { if (this.autoFlush) { + // @ts-ignore this.raw[name](...args) } else { this.buffer.push({ name, args }) @@ -265,7 +304,7 @@ class LogRouter { } } - handleError(...args) { + handleError(...args: any[]) { API.remoteLog('error', args) } } @@ -275,6 +314,7 @@ let logRouter = new LogRouter() function hideLogs() { console.log('All subsequent logs will be hidden. Eval `showLogs()` to reveal them.') logRouter.autoFlush = false + // @ts-ignore console.autoFlush = false } @@ -282,6 +322,7 @@ function showLogs() { logRouter.auto_flush_on() } +// @ts-ignore window.showLogs = showLogs // ====================== @@ -306,7 +347,7 @@ function getPreviousCrashMessage() { return sessionStorage.getItem(crashMessageStorageKey) } -function storeLastCrashMessage(message) { +function storeLastCrashMessage(message: string) { sessionStorage.setItem(crashMessageStorageKey, message) } @@ -336,13 +377,14 @@ function setupCrashDetection() { }) } -function handleCrash(message) { +function handleCrash(message: string) { API.remoteLog('crash', message) if (document.getElementById(crashBannerId) === null) { storeLastCrashMessage(message) location.reload() } else { for (let element of [...document.body.childNodes]) { + // @ts-ignore if (element.id !== crashBannerId) { element.remove() } @@ -363,7 +405,7 @@ const crashBannerContentId = 'crash-banner-content' const crashReportButtonId = 'crash-report-button' const crashBannerCloseButtonId = 'crash-banner-close-button' -function showCrashBanner(message) { +function showCrashBanner(message: string) { document.body.insertAdjacentHTML( 'afterbegin', `
@@ -393,7 +435,8 @@ function showCrashBanner(message) { } } -async function reportCrash(message) { +async function reportCrash(message: string) { + // @ts-ignore const crashReportHost = API[globalConfig.windowAppScopeConfigName].crash_report_host await fetch(`http://${crashReportHost}/`, { method: 'POST', @@ -417,6 +460,7 @@ function style_root() { /// Waits for the window to finish its show animation. It is used when the website is run in /// Electron. Please note that it returns immediately in the web browser. async function windowShowAnimation() { + // @ts-ignore await window.showAnimation } @@ -426,41 +470,104 @@ function disableContextMenu() { }) } -function ok(value) { +function ok(value: any) { return value !== null && value !== undefined } -/// Main entry point. Loads WASM, initializes it, chooses the scene to run. -API.main = async function (inputConfig) { - let defaultConfig = { - use_loader: true, - wasm_url: '/assets/ide.wasm', - wasm_glue_url: '/assets/wasm_imports.js', - crash_report_host: cfg.defaultLogServerHost, - no_data_gathering: false, - is_in_cloud: false, +class Config { + public use_loader: boolean + public wasm_url: string + public wasm_glue_url: string + public crash_report_host: string + public no_data_gathering: boolean + public is_in_cloud: boolean + public entry: string + + static default() { + let config = new Config() + config.use_loader = true + config.wasm_url = '/assets/ide.wasm' + config.wasm_glue_url = '/assets/wasm_imports.js' + config.crash_report_host = cfg.defaultLogServerHost + config.no_data_gathering = false + config.is_in_cloud = false + config.entry = null + return config } - let urlParams = new URLSearchParams(window.location.search) - let urlConfig = Object.fromEntries(urlParams.entries()) - let config = Object.assign(defaultConfig, inputConfig, urlConfig) - API[globalConfig.windowAppScopeConfigName] = config - if (config.no_data_gathering) { - API.remoteLog = function (_event, _data) {} - } else { - let logger = new MixpanelLogger() - API.remoteLog = function (event, data) { - logger.log(event, data) + updateFromObject(other: any) { + if (!ok(other)) { + return } + this.use_loader = ok(other.use_loader) ? tryAsBoolean(other.use_loader) : this.use_loader + this.no_data_gathering = ok(other.no_data_gathering) + ? tryAsBoolean(other.no_data_gathering) + : this.no_data_gathering + this.is_in_cloud = ok(other.is_in_cloud) + ? tryAsBoolean(other.is_in_cloud) + : this.is_in_cloud + this.wasm_url = ok(other.wasm_url) ? tryAsString(other.wasm_url) : this.wasm_url + this.wasm_glue_url = ok(other.wasm_glue_url) + ? tryAsString(other.wasm_glue_url) + : this.wasm_glue_url + this.crash_report_host = ok(other.crash_report_host) + ? tryAsString(other.crash_report_host) + : this.crash_report_host + this.entry = ok(other.entry) ? tryAsString(other.entry) : this.entry } +} + +/// Check whether the value is a string with value `"true"`/`"false"`, if so, return the +// appropriate boolean instead. Otherwise, return the original value. +function parseBooleanOrLeaveAsIs(value: any): any { + if (value === 'true') { + return true + } + if (value === 'false') { + return false + } + return value +} + +function tryAsBoolean(value: any): boolean { + value = parseBooleanOrLeaveAsIs(value) + assert(typeof value == 'boolean') + return value +} + +function tryAsString(value: any): string { + return value.toString() +} + +/// Main entry point. Loads WASM, initializes it, chooses the scene to run. +API.main = async function (inputConfig: any) { + const urlParams = new URLSearchParams(window.location.search) + // @ts-ignore + const urlConfig = Object.fromEntries(urlParams.entries()) + + const config = Config.default() + config.updateFromObject(inputConfig) + config.updateFromObject(urlConfig) + + // @ts-ignore + API[globalConfig.windowAppScopeConfigName] = config + + API.initLogging(config) window.setInterval(() => { API.remoteLog('alive') }, ALIVE_LOG_INTERVAL) - //Build data injected during the build process. See `webpack.config.js` for the source. - API.remoteLog('git_hash', { hash: GIT_HASH }) - API.remoteLog('build_information', BUILD_INFO) - API.remoteLog('git_status', { satus: GIT_STATUS }) + + // Build data injected during the build process. See `webpack.config.js` for the source. + // @ts-ignore + const hash = GIT_HASH + API.remoteLog('git_hash', { hash }) + // @ts-ignore + const buildInfo = BUILD_INFO + API.remoteLog('build_information', buildInfo) + // @ts-ignore + const status = GIT_STATUS + API.remoteLog('git_status', { status }) //initCrashHandling() style_root() @@ -486,6 +593,6 @@ API.main = async function (inputConfig) { show_debug_screen(wasm, "Unknown entry point '" + entryTarget + "'. ") } } else { - show_debug_screen(wasm) + show_debug_screen(wasm, '') } }