From de476f60bbe6a6f315c43c6c801dc2a8fe0abf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Mar=C3=A9chal?= Date: Thu, 28 Jun 2018 14:46:06 -0400 Subject: [PATCH] [example-hybrid] Connect Electron to a server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new example package serving both web browsers and electron clients. Add new commands for electron to connect to remote servers. `electron.remote.connect` command to open a QuickOpenMenu and input an URL. `electron.remote.history.clear` command to clear the local URL history. `electron.remote.disconnect` to close the window in case you are connected to a server. Adds an endpoint to tell if it is a Theia application or not. Signed-off-by: Paul Maréchal --- .../src/application-package-manager.ts | 19 +- .../src/generator/abstract-generator.ts | 12 + .../src/generator/backend-generator.ts | 33 +- .../src/generator/frontend-generator.ts | 75 +++- .../src/generator/webpack-generator.ts | 51 ++- .../application-manager/src/rebuild.ts | 7 +- .../src/application-package.ts | 12 +- examples/hybrid/compile.tsconfig.json | 3 + examples/hybrid/package.json | 59 +++ packages/core/package.json | 8 +- packages/core/src/browser/browser.ts | 5 + .../browser/quick-open/quick-open-model.ts | 2 +- packages/core/src/common/promise-util.ts | 10 + .../common/remote/electron-remote-protocol.ts | 23 ++ .../menu/electron-menu-contribution.ts | 9 +- .../remote/electron-backend-location.ts | 35 ++ .../remote/electron-remote-contribution.ts | 357 ++++++++++++++++++ .../remote/electron-remote-module.ts | 33 ++ .../electron-remote-backend-contribution.ts | 33 ++ .../remote/electron-remote-module.ts | 29 ++ packages/monaco/src/browser/monaco-loader.ts | 2 +- .../monaco-electron-module.ts | 18 +- yarn.lock | 264 ++++++++++++- 23 files changed, 1047 insertions(+), 52 deletions(-) create mode 100644 examples/hybrid/compile.tsconfig.json create mode 100644 examples/hybrid/package.json create mode 100644 packages/core/src/common/remote/electron-remote-protocol.ts create mode 100644 packages/core/src/electron-browser/remote/electron-backend-location.ts create mode 100644 packages/core/src/electron-browser/remote/electron-remote-contribution.ts create mode 100644 packages/core/src/electron-browser/remote/electron-remote-module.ts create mode 100644 packages/core/src/electron-node/remote/electron-remote-backend-contribution.ts create mode 100644 packages/core/src/electron-node/remote/electron-remote-module.ts diff --git a/dev-packages/application-manager/src/application-package-manager.ts b/dev-packages/application-manager/src/application-package-manager.ts index deb08c5fe9b95..5c80e467c8661 100644 --- a/dev-packages/application-manager/src/application-package-manager.ts +++ b/dev-packages/application-manager/src/application-package-manager.ts @@ -61,7 +61,12 @@ export class ApplicationPackageManager { async copy(): Promise { await fs.ensureDir(this.pck.lib()); - await fs.copy(this.pck.frontend('index.html'), this.pck.lib('index.html')); + if (this.pck.isHybrid()) { + await fs.copy(this.pck.frontend('electron', 'index.html'), this.pck.lib('electron', 'index.html')); + await fs.copy(this.pck.frontend('browser', 'index.html'), this.pck.lib('browser', 'index.html')); + } else { + await fs.copy(this.pck.frontend('index.html'), this.pck.lib('index.html')); + } } async build(args: string[] = []): Promise { @@ -73,8 +78,14 @@ export class ApplicationPackageManager { async start(args: string[] = []): Promise { if (this.pck.isElectron()) { return this.startElectron(args); + + } else if (this.pck.isBrowser()) { + return this.startBrowser(args); + + } else if (this.pck.isHybrid()) { + return this.startHybrid(args); } - return this.startBrowser(args); + throw new Error(`Unknown target: '${this.pck.target}'`); } async startElectron(args: string[]): Promise { @@ -99,4 +110,8 @@ export class ApplicationPackageManager { this.__process.fork(this.pck.backend('main.js'), mainArgs, options); } + async startHybrid(args: string[]): Promise { + return this.startBrowser(args); + } + } diff --git a/dev-packages/application-manager/src/generator/abstract-generator.ts b/dev-packages/application-manager/src/generator/abstract-generator.ts index 6c4f1e337873c..93263b64768d2 100644 --- a/dev-packages/application-manager/src/generator/abstract-generator.ts +++ b/dev-packages/application-manager/src/generator/abstract-generator.ts @@ -59,6 +59,14 @@ export abstract class AbstractGenerator { return os.EOL + lines.join(os.EOL); } + get package(): ApplicationPackage { + return this.pck; + } + + normalized(string: string): string { + return string.replace(/\W/, ''); + } + protected ifBrowser(value: string, defaultValue: string = '') { return this.pck.ifBrowser(value, defaultValue); } @@ -67,6 +75,10 @@ export abstract class AbstractGenerator { return this.pck.ifElectron(value, defaultValue); } + protected ifHybrid(value: string, defaultValue: string = '') { + return this.pck.ifHybrid(value, defaultValue); + } + protected async write(path: string, content: string): Promise { await fs.ensureFile(path); await fs.writeFile(path, content); diff --git a/dev-packages/application-manager/src/generator/backend-generator.ts b/dev-packages/application-manager/src/generator/backend-generator.ts index 20d220bbfeabc..d8aa9040b7e12 100644 --- a/dev-packages/application-manager/src/generator/backend-generator.ts +++ b/dev-packages/application-manager/src/generator/backend-generator.ts @@ -24,8 +24,32 @@ export class BackendGenerator extends AbstractGenerator { await this.write(this.pck.backend('main.js'), this.compileMain(backendModules)); } + protected compileExpressStatic(segment: string = ''): string { + return `express.static(path.join(__dirname, '../../lib${segment}'), { + index: 'index.html' + })`; + } + + protected compileMiddleware(backendModules: Map): string { + return this.pck.isHybrid() ? ` + const electron = ${this.compileExpressStatic('/electron')}; + const browser = ${this.compileExpressStatic('/browser')}; + + application.use('*', (request, ...args) => { + const userAgent = request.headers['user-agent'] || 'unknown'; + const isElectron = /electron/ig.test(userAgent); + request.url = request.baseUrl || request.url; + return (isElectron ? + electron : browser)(request, ...args); + }); +` : ` + application.use(${this.compileExpressStatic()}); +`; + } + protected compileServer(backendModules: Map): string { - return `// @ts-check + return `\ +// @ts-check require('reflect-metadata'); const path = require('path'); const express = require('express'); @@ -51,9 +75,7 @@ function start(port, host) { const cliManager = container.get(CliManager); return cliManager.initializeCli().then(function () { const application = container.get(BackendApplication); - application.use(express.static(path.join(__dirname, '../../lib'), { - index: 'index.html' - })); +${this.compileMiddleware(backendModules)} return application.start(port, host); }); } @@ -69,7 +91,8 @@ module.exports = (port, host) => Promise.resolve()${this.compileBackendModuleImp } protected compileMain(backendModules: Map): string { - return `// @ts-check + return `\ +// @ts-check const serverPath = require('path').resolve(__dirname, 'server'); const address = require('@theia/core/lib/node/cluster/main').default(serverPath); address.then(function (address) { diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index 6861025dd55e5..9450e886ecf41 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -20,15 +20,25 @@ export class FrontendGenerator extends AbstractGenerator { async generate(): Promise { const frontendModules = this.pck.targetFrontendModules; - await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules)); - await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules)); + + if (this.package.isHybrid()) { + await this.write(this.pck.frontend('browser', 'index.html'), this.compileIndexHtml(this.package.frontendModules)); + await this.write(this.pck.frontend('electron', 'index.html'), this.compileIndexHtml(this.package.frontendElectronModules)); + await this.write(this.pck.frontend('browser', 'index.js'), this.compileIndexJs(this.package.frontendModules)); + await this.write(this.pck.frontend('electron', 'index.js'), this.compileIndexJs(this.package.frontendElectronModules)); + } else { + await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules)); + await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules)); + } + if (this.pck.isElectron()) { await this.write(this.pck.frontend('electron-main.js'), this.compileElectronMain()); } } protected compileIndexHtml(frontendModules: Map): string { - return ` + return `\ + ${this.compileIndexHead(frontendModules)} @@ -48,8 +58,10 @@ export class FrontendGenerator extends AbstractGenerator { } protected compileIndexJs(frontendModules: Map): string { - return `// @ts-check -${this.ifBrowser("require('es6-promise/auto');")} + return `\ +// @ts-check +${this.ifBrowser(`require('es6-promise/auto'); +`)}\ require('reflect-metadata'); const { Container } = require('inversify'); const { FrontendApplication } = require('@theia/core/lib/browser'); @@ -87,8 +99,8 @@ module.exports = Promise.resolve()${this.compileFrontendModuleImports(frontendMo } protected compileElectronMain(): string { - return `// @ts-check - + return `\ +// @ts-check // Workaround for https://github.com/electron/electron/issues/9225. Chrome has an issue where // in certain locales (e.g. PL), image metrics are wrongly computed. We explicitly set the // LC_NUMERIC to prevent this from happening (selects the numeric formatting category of the @@ -102,18 +114,45 @@ const { join } = require('path'); const { isMaster } = require('cluster'); const { fork } = require('child_process'); const { app, BrowserWindow, ipcMain } = require('electron'); +const EventEmitter = require('events'); +const fileSchemeTester = /^file:/; +const localUriEvent = new EventEmitter(); +let localUri = undefined; const windows = []; +function setLocalUri(uri) { + localUriEvent.emit('update', localUri = uri); +} +function resolveLocalUriFromPort(port) { + setLocalUri('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port); +} + function createNewWindow(theUrl) { - const newWindow = new BrowserWindow({ width: 1024, height: 728, show: !!theUrl }); + const config = { + width: 1024, + height: 728, + show: !!theUrl + }; + + // Converts 'localhost' to the running local backend endpoint + if (localUri && theUrl === 'localhost') { + theUrl = localUri; + } + + if (!!theUrl && !fileSchemeTester.test(theUrl)) { + config.webPreferences = { + // nodeIntegration: false, + // contextIsolation: true, + }; + }; + + const newWindow = new BrowserWindow(config); if (windows.length === 0) { newWindow.webContents.on('new-window', (event, url, frameName, disposition, options) => { // If the first electron window isn't visible, then all other new windows will remain invisible. // https://github.com/electron/electron/issues/3751 - options.show = true; - options.width = 1024; - options.height = 728; + Object.assign(options, config); }); } windows.push(newWindow); @@ -145,17 +184,21 @@ if (isMaster) { }); app.on('ready', () => { // Check whether we are in bundled application or development mode. - const devMode = process.defaultApp || /node_modules[\/]electron[\/]/.test(process.execPath); + const devMode = process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath); + const mainWindow = createNewWindow(); - const loadMainWindow = (port) => { - mainWindow.loadURL('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port); + const loadMainWindow = (uri) => { + // mainWindow.loadURL(\`http://localhost:\${port}\`); + mainWindow.loadURL(uri); }; + localUriEvent.once('update', loadMainWindow); + const mainPath = join(__dirname, '..', 'backend', 'main'); // We need to distinguish between bundled application and development mode when starting the clusters. // See: https://github.com/electron/electron/issues/6337#issuecomment-230183287 if (devMode) { require(mainPath).then(address => { - loadMainWindow(address.port); + resolveLocalUriFromPort(address.port) }).catch((error) => { console.error(error); app.exit(1); @@ -163,7 +206,7 @@ if (isMaster) { } else { const cp = fork(mainPath); cp.on('message', (message) => { - loadMainWindow(message); + resolveLocalUriFromPort(message); }); cp.on('error', (error) => { console.error(error); diff --git a/dev-packages/application-manager/src/generator/webpack-generator.ts b/dev-packages/application-manager/src/generator/webpack-generator.ts index 0c0660fd1cd79..4e884e3e02136 100644 --- a/dev-packages/application-manager/src/generator/webpack-generator.ts +++ b/dev-packages/application-manager/src/generator/webpack-generator.ts @@ -16,11 +16,12 @@ import * as paths from 'path'; import { AbstractGenerator } from './abstract-generator'; +import { ApplicationPackageTarget } from '@theia/application-package'; export class WebpackGenerator extends AbstractGenerator { async generate(): Promise { - await this.write(this.configPath, this.compileWebpackConfig()); + await this.write(this.configPath, this.compileWebpackConfigFile()); } get configPath(): string { @@ -31,7 +32,18 @@ export class WebpackGenerator extends AbstractGenerator { return this.pck.resolveModulePath(moduleName, path).split(paths.sep).join('/'); } - protected compileWebpackConfig(): string { + protected compileExports(): string { + const exports = []; + if (this.package.isHybrid() || this.package.isBrowser()) { + exports.push(this.normalized('browser')); + } + if (this.package.isHybrid() || this.package.isElectron()) { + exports.push(this.normalized('electron')); + } + return `[${exports.join(', ')}]`; + } + + protected compileWebpackConfigFile(): string { return `// @ts-check const path = require('path'); const webpack = require('webpack'); @@ -45,27 +57,38 @@ const { mode } = yargs.option('mode', { choices: ["development", "production"], default: "production" }).argv; -const development = mode === 'development';${this.ifMonaco(() => ` - +const development = mode === 'development'; +${this.ifMonaco(() => ` const monacoEditorCorePath = development ? '${this.resolve('monaco-editor-core', 'dev/vs')}' : '${this.resolve('monaco-editor-core', 'min/vs')}'; const monacoCssLanguagePath = '${this.resolve('monaco-css', 'release/min')}'; -const monacoHtmlLanguagePath = '${this.resolve('monaco-html', 'release/min')}';`)} +const monacoHtmlLanguagePath = '${this.resolve('monaco-html', 'release/min')}'; +`)} +${this.package.isHybrid() || this.package.isBrowser() ? this.compileWebpackConfigObjectFor('browser') : ''}\ +${this.package.isHybrid() || this.package.isElectron() ? this.compileWebpackConfigObjectFor('electron') : ''}\ -module.exports = { - entry: path.resolve(__dirname, 'src-gen/frontend/index.js'), +module.exports = ${this.compileExports()}; +`; + } + + protected compileWebpackConfigObjectFor(target: ApplicationPackageTarget): string { + const normalizedTarget = this.normalized(target); + return `\ +const ${normalizedTarget} = { + entry: path.resolve(__dirname, 'src-gen/frontend/${this.ifHybrid(normalizedTarget + '/')}index.js'), output: { filename: 'bundle.js', - path: outputPath + path: path.resolve(outputPath, '${this.ifHybrid(normalizedTarget)}') }, - target: '${this.ifBrowser('web', 'electron-renderer')}', + target: '${target === 'browser' ? 'web' : 'electron-renderer'}', mode, - node: {${this.ifElectron(` + node: {${target === 'electron' ? ` __dirname: false, - __filename: false`, ` + __filename: false` + : /* else */ ` fs: 'empty', child_process: 'empty', net: 'empty', - crypto: 'empty'`)} + crypto: 'empty'` } }, module: { rules: [ @@ -152,7 +175,7 @@ module.exports = { stats: { warnings: true } -};`; +}; +`; } - } diff --git a/dev-packages/application-manager/src/rebuild.ts b/dev-packages/application-manager/src/rebuild.ts index ff3101a0da43e..01010535a5eb4 100644 --- a/dev-packages/application-manager/src/rebuild.ts +++ b/dev-packages/application-manager/src/rebuild.ts @@ -14,11 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { ApplicationPackageTarget } from '@theia/application-package/lib/'; import fs = require('fs-extra'); import path = require('path'); import cp = require('child_process'); -export function rebuild(target: 'electron' | 'browser', modules: string[]) { +export function rebuild(target: ApplicationPackageTarget, modules: string[]) { const nodeModulesPath = path.join(process.cwd(), 'node_modules'); const browserModulesPath = path.join(process.cwd(), '.browser_modules'); const modulesToProcess = modules || ['node-pty', 'vscode-nsfw', 'find-git-repositories']; @@ -54,7 +55,8 @@ export function rebuild(target: 'electron' | 'browser', modules: string[]) { fs.writeFile(packFile, packageText); }, 100); } - } else if (target === 'browser' && fs.existsSync(browserModulesPath)) { + + } else if (/^(browser|hybrid)$/.test(target) && fs.existsSync(browserModulesPath)) { for (const moduleName of fs.readdirSync(browserModulesPath)) { console.log("Reverting " + moduleName); const src = path.join(browserModulesPath, moduleName); @@ -63,6 +65,7 @@ export function rebuild(target: 'electron' | 'browser', modules: string[]) { fs.copySync(src, dest); } fs.removeSync(browserModulesPath); + } else { console.log('native node modules are already rebuilt for ' + target); } diff --git a/dev-packages/application-package/src/application-package.ts b/dev-packages/application-package/src/application-package.ts index 623c670329fee..3bf55b652cb10 100644 --- a/dev-packages/application-package/src/application-package.ts +++ b/dev-packages/application-package/src/application-package.ts @@ -21,7 +21,7 @@ import { NpmRegistry, NpmRegistryConfig, NodePackage, PublishedNodePackage, sort import { Extension, ExtensionPackage, RawExtensionPackage } from './extension-package'; import { ExtensionPackageCollector } from './extension-package-collector'; -export type ApplicationPackageTarget = 'browser' | 'electron'; +export type ApplicationPackageTarget = 'browser' | 'electron' | 'hybrid'; export class ApplicationPackageConfig extends NpmRegistryConfig { readonly target: ApplicationPackageTarget; } @@ -206,6 +206,10 @@ export class ApplicationPackage { return this.target === 'electron'; } + isHybrid(): boolean { + return this.target === 'hybrid'; + } + ifBrowser(value: T): T | undefined; ifBrowser(value: T, defaultValue: T): T; ifBrowser(value: T, defaultValue?: T): T | undefined { @@ -218,6 +222,12 @@ export class ApplicationPackage { return this.isElectron() ? value : defaultValue; } + ifHybrid(value: T): T | undefined; + ifHybrid(value: T, defaultValue: T): T; + ifHybrid(value: T, defaultValue?: T): T | undefined { + return this.isHybrid() ? value : defaultValue; + } + get targetBackendModules(): Map { return this.ifBrowser(this.backendModules, this.backendElectronModules); } diff --git a/examples/hybrid/compile.tsconfig.json b/examples/hybrid/compile.tsconfig.json new file mode 100644 index 0000000000000..0c45f79cfd764 --- /dev/null +++ b/examples/hybrid/compile.tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../configs/base.tsconfig" +} \ No newline at end of file diff --git a/examples/hybrid/package.json b/examples/hybrid/package.json new file mode 100644 index 0000000000000..b8bb37f944f1a --- /dev/null +++ b/examples/hybrid/package.json @@ -0,0 +1,59 @@ +{ + "private": true, + "name": "@theia/example-hybrid", + "version": "0.3.11", + "theia": { + "target": "hybrid" + }, + "dependencies": { + "@theia/callhierarchy": "^0.3.11", + "@theia/core": "^0.3.11", + "@theia/cpp": "^0.3.11", + "@theia/editor": "^0.3.11", + "@theia/editorconfig": "^0.3.11", + "@theia/extension-manager": "^0.3.11", + "@theia/file-search": "^0.3.11", + "@theia/filesystem": "^0.3.11", + "@theia/git": "^0.3.11", + "@theia/java": "^0.3.11", + "@theia/keymaps": "^0.3.11", + "@theia/languages": "^0.3.11", + "@theia/markers": "^0.3.11", + "@theia/merge-conflicts": "^0.3.11", + "@theia/messages": "^0.3.11", + "@theia/metrics": "^0.3.11", + "@theia/mini-browser": "^0.3.11", + "@theia/monaco": "^0.3.11", + "@theia/navigator": "^0.3.11", + "@theia/outline-view": "^0.3.11", + "@theia/output": "^0.3.11", + "@theia/plugin-ext": "^0.3.11", + "@theia/plugin-ext-vscode": "^0.3.11", + "@theia/preferences": "^0.3.11", + "@theia/preview": "^0.3.11", + "@theia/process": "^0.3.11", + "@theia/python": "^0.3.11", + "@theia/search-in-workspace": "^0.3.11", + "@theia/task": "^0.3.11", + "@theia/terminal": "^0.3.11", + "@theia/typescript": "^0.3.11", + "@theia/userstorage": "^0.3.11", + "@theia/variable-resolver": "^0.3.11", + "@theia/workspace": "^0.3.11" + }, + "scripts": { + "prepare": "yarn run clean && yarn build", + "clean": "theia clean", + "build": "theia build --mode development", + "watch": "yarn build --watch --mode development", + "start": "theia start", + "start:debug": "yarn start --log-level=debug", + "test": "electron-mocha --timeout 60000 --require ts-node/register \"./test/**/*.espec.ts\"", + "test:ui": "wdio wdio.conf.js" + }, + "devDependencies": { + "@theia/cli": "^0.3.11", + "webpack": "^4.13.0", + "webpack-cli": "^3.0.8" + } +} diff --git a/packages/core/package.json b/packages/core/package.json index 07ec889a723e0..5073d3789ee94 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -19,6 +19,7 @@ "@types/ws": "^3.0.2", "@types/yargs": "^8.0.2", "ajv": "^5.2.2", + "axios": "^0.18.0", "body-parser": "^1.17.2", "electron": "1.8.2-beta.5", "es6-promise": "^4.2.4", @@ -45,14 +46,17 @@ "publishConfig": { "access": "public" }, - "theiaExtensions": [ - { + "theiaExtensions": [{ "frontend": "lib/browser/menu/browser-menu-module", "frontendElectron": "lib/electron-browser/menu/electron-menu-module" }, { "frontend": "lib/browser/window/browser-window-module", "frontendElectron": "lib/electron-browser/window/electron-window-module" + }, + { + "frontendElectron": "lib/electron-browser/remote/electron-remote-module", + "backendElectron": "lib/electron-node/remote/electron-remote-module" } ], "keywords": [ diff --git a/packages/core/src/browser/browser.ts b/packages/core/src/browser/browser.ts index 156d4e2a97771..d789fc41634a5 100644 --- a/packages/core/src/browser/browser.ts +++ b/packages/core/src/browser/browser.ts @@ -35,6 +35,11 @@ export const isNative = typeof (window as any).process !== 'undefined'; // tslint:disable-next-line:no-any export const isBasicWasmSupported = typeof (window as any).WebAssembly !== 'undefined'; +// Current protocol in use to display the application +export const isHttps = /^https:/.test(self.location.href); +export const isHttp = /^http:/.test(self.location.href); +export const isFile = /^file:/.test(self.location.href); + /** * Parse a magnitude value (e.g. width, height, left, top) from a CSS attribute value. * Returns the given default value (or undefined) if the value cannot be determined, diff --git a/packages/core/src/browser/quick-open/quick-open-model.ts b/packages/core/src/browser/quick-open/quick-open-model.ts index 77f48a2b7ba8f..8ccdc63b13f02 100644 --- a/packages/core/src/browser/quick-open/quick-open-model.ts +++ b/packages/core/src/browser/quick-open/quick-open-model.ts @@ -75,7 +75,7 @@ export class QuickOpenItem { this.reject = reject; }); } + +/** + * Simple timeout-as-promise implementation. + * + * @param time delay to wait for + * @param value value to resolve to after the timeout + */ +export function timeout(time: Number, value?: T): Promise { + return new Promise(resolve => setTimeout(resolve, time, value)); +} diff --git a/packages/core/src/common/remote/electron-remote-protocol.ts b/packages/core/src/common/remote/electron-remote-protocol.ts new file mode 100644 index 0000000000000..1d2e60605153b --- /dev/null +++ b/packages/core/src/common/remote/electron-remote-protocol.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export const ElectronRemoteQuestionPath = Symbol('ElectronRemoteQuestionPath'); +export const ElectronRemoteAnswer = Symbol('ElectronRemoteAnswer'); + +export const defaultElectronRemoteQuestionPath = '/are-you-theia'; +export const defaultElectronRemoteAnswer = { + answer: 'yes-I-am-Theia', +}; diff --git a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts index 2f4997eae918e..793b70212ce5f 100644 --- a/packages/core/src/electron-browser/menu/electron-menu-contribution.ts +++ b/packages/core/src/electron-browser/menu/electron-menu-contribution.ts @@ -20,9 +20,10 @@ import { Command, CommandContribution, CommandRegistry, MenuModelRegistry, MenuContribution } from '../../common'; -import { KeybindingContribution, KeybindingRegistry } from '../../browser'; +import { KeybindingContribution, KeybindingRegistry, QuickOpenService } from '../../browser'; import { FrontendApplication, FrontendApplicationContribution, CommonMenus } from '../../browser'; import { ElectronMainMenuFactory } from './electron-main-menu-factory'; +import { WindowService } from '../../browser/window/window-service'; export namespace ElectronCommands { export const TOGGLE_DEVELOPER_TOOLS: Command = { @@ -59,6 +60,12 @@ export namespace ElectronMenus { @injectable() export class ElectronMenuContribution implements FrontendApplicationContribution, CommandContribution, MenuContribution, KeybindingContribution { + @inject(QuickOpenService) + protected readonly quickOpenService: QuickOpenService; + + @inject(WindowService) + protected readonly windowService: WindowService; + constructor( @inject(ElectronMainMenuFactory) protected readonly factory: ElectronMainMenuFactory ) { } diff --git a/packages/core/src/electron-browser/remote/electron-backend-location.ts b/packages/core/src/electron-browser/remote/electron-backend-location.ts new file mode 100644 index 0000000000000..d9994ca033dbd --- /dev/null +++ b/packages/core/src/electron-browser/remote/electron-backend-location.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { isFile } from '../../browser'; + +export type ElectronBackendLocation = 'local' | 'remote'; +export namespace ElectronBackendLocation { + + export const location: ElectronBackendLocation = isFile ? 'local' : 'remote'; + + export function getLocation(): ElectronBackendLocation { + return location; + } + + export function isLocal(): boolean { + return location === 'local'; + } + + export function isRemote(): boolean { + return location === 'remote'; + } +} diff --git a/packages/core/src/electron-browser/remote/electron-remote-contribution.ts b/packages/core/src/electron-browser/remote/electron-remote-contribution.ts new file mode 100644 index 0000000000000..0b7b2705df18e --- /dev/null +++ b/packages/core/src/electron-browser/remote/electron-remote-contribution.ts @@ -0,0 +1,357 @@ +/******************************************************************************** + * Copyright (C) 2017 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +// tslint:disable:no-any + +import { join } from 'path'; +import { inject, injectable } from 'inversify'; +import Axios, { AxiosResponse } from 'axios'; +import URI from '../../common/uri'; +import { + Command, CommandContribution, CommandRegistry, + MenuModelRegistry, MenuContribution, ILogger +} from '../../common'; +import { + StorageService, KeybindingContribution, KeybindingRegistry, + QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenMode, QuickOpenGroupItem, QuickOpenGroupItemOptions +} from '../../browser'; +import { CommonMenus } from '../../browser'; +import { WindowService } from '../../browser/window/window-service'; +import { timeout as delay } from '../../common/promise-util'; +import { ElectronBackendLocation } from './electron-backend-location'; +import { ElectronRemoteQuestionPath, ElectronRemoteAnswer } from '../../common/remote/electron-remote-protocol'; + +export type RemoteEntryGroup = 'Input' | 'Autocomplete' | 'History'; +export type Response = AxiosResponse; +export interface RemoteEntryOptions { + errorMessage?: string; + questionPath?: string; + expectedAnswer?: any; +} +export class RemoteEntry { + + protected _ok?: boolean; + protected _response?: Response; + protected _error?: Error; + + constructor( + public url: string, + protected options: RemoteEntryOptions = {}, + public group?: string, + ) { } + + protected get errorMessage(): string | undefined { + return this.options.errorMessage; + } + protected get questionPath(): string | undefined { + return this.options.questionPath; + } + protected get expectedAnswer(): object | undefined { + return this.options.expectedAnswer; + } + + protected createError(response: Response): Error { + return new Error(this.errorMessage || response.statusText); + } + + protected compareAnswer(answer: any, expected: any): boolean { + return Object.keys(answer) + .filter(key => answer.hasOwnProperty(key)) + .every(key => answer[key] === expected[key]) + ; + } + + protected coerce(response: Response): Response { + if (/^2/.test(response.status.toString())) { + if (this.expectedAnswer) { + if (!(this._ok = this.compareAnswer(response.data, this.expectedAnswer))) { + throw this.createError(response); + } + } + return response; + } + throw this.createError(response); + } + + async poll(timeout?: number): Promise { + if (this._error) { + throw this._error; + } + if (this._response) { + return this._response; + } + const url = this.questionPath ? join(this.url, this.questionPath) : this.url; + try { + return this.coerce(this._response = await Axios.get(url, { timeout, responseType: 'json' })); + } catch (error) { + if (error.response && /^4/.test(error.response.status.toString())) { + error = this.createError(error.response); + } + throw this._error = error; + } + } + + get response(): RemoteEntry['_response'] { + if (this._error) { + throw this._error; + } + return this._response; + } + + hasError(): boolean { + return typeof this._error !== 'undefined'; + } + + hasResponse(): boolean { + return typeof this.response !== 'undefined'; + } + + isOk(): boolean { + return !!this._ok; + } + + getStatusText(): string { + try { + const response = this.response; + if (response) { + return 'Online'; + } + } catch (error) { + return error.message; + } + return 'Unresolved'; + } + + clear(): void { + this._response = undefined; + this._error = undefined; + this._ok = undefined; + } +} + +@injectable() +export class ElectronRemoteContribution implements QuickOpenModel, CommandContribution, MenuContribution, KeybindingContribution { + + @inject(StorageService) protected readonly localStorageService: StorageService; + @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService; + @inject(WindowService) protected readonly windowService: WindowService; + @inject(ILogger) protected readonly logger: ILogger; + + protected historyEntries: Promise; + protected timeout: number = 500; // ms + + constructor( + @inject(ElectronRemoteQuestionPath) protected readonly questionPath?: string, + @inject(ElectronRemoteAnswer) protected readonly expectedAnswer?: object, + ) { } + + protected remoteEntryOptions: RemoteEntryOptions = { + errorMessage: 'Not a Theia Application', + expectedAnswer: this.expectedAnswer, + questionPath: this.questionPath, + }; + + protected get history(): Promise { + return this.localStorageService.getData(ElectronRemoteHistory.KEY, []) + .then(history => history.map(entry => decodeURI(entry))); + } + + protected async remember(url: string): Promise { + const history = await this.localStorageService.getData(ElectronRemoteHistory.KEY, []); + const encoded = encodeURI(url); + if (encoded) { + const index = history.indexOf(encoded); + if (index >= 0) { + history.splice(index); + } + history.unshift(encoded); + this.localStorageService.setData(ElectronRemoteHistory.KEY, history); + } + } + + protected async clearHistory(): Promise { + return this.localStorageService.setData(ElectronRemoteHistory.KEY, undefined); + } + + protected async computeHistoryCache(): Promise { + return this.accumulateResponses( + (await this.history).map( + url => new RemoteEntry(url, this.remoteEntryOptions, 'History') + ), this.timeout); + } + + protected async accumulateResponses(input: RemoteEntry[], timeout: number): Promise { + const output: RemoteEntry[] = []; + input.forEach(async entry => { + await entry.poll(timeout).catch(e => void 0); + output.push(entry); + }); + await delay(timeout); + return output.slice(0); + } + + protected urlOpener = (url: string) => (mode: QuickOpenMode): boolean => { + if (mode === QuickOpenMode.OPEN) { + this.windowService.openNewWindow(url); + this.remember(url); + } + return true; + } + + protected convertEntryToQuickOpenItem(entry: RemoteEntry, override: QuickOpenGroupItemOptions = {}): QuickOpenItem { + const opener = this.urlOpener(entry.url); + return new QuickOpenGroupItem({ + label: entry.url, + groupLabel: entry.group, + description: entry.getStatusText(), + run: mode => entry.isOk() ? opener(mode) : false, + ...override, + }); + } + + async onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): Promise { + const defaultSchemes = ['http', 'https']; + const items: QuickOpenItem[] = []; + const inputResponses = []; + const inputEntries = []; + + // Add a way to open a local electron window + if (ElectronBackendLocation.isRemote()) { + items.push(new QuickOpenGroupItem({ + label: 'Localhost Application', + groupLabel: 'Electron', + description: 'Electron', + run: this.urlOpener('localhost'), + })); + } + + if (lookFor) { + let url = new URI(lookFor); + + // Autocompletion (http/https) if not using http(s) filescheme + if (!/^https?$/.test(url.scheme)) { + const reformated = new URI(`//${lookFor}`); + for (const scheme of defaultSchemes) { + url = reformated.withScheme(scheme); + inputEntries.push( + new RemoteEntry(url.toString(), this.remoteEntryOptions, 'Autocomplete') + ); + } + } else { + inputEntries.push( + new RemoteEntry(url.toString(), this.remoteEntryOptions, 'Input') + ); + } + + // Host polling + inputResponses.push(...await this.accumulateResponses(inputEntries, this.timeout)); + } + + // Sorting the autocompletion and history based on the status of the responses + const sortedEntries = [...inputResponses, ...await this.historyEntries] + // make unique + .filter((entry, index, array) => array.findIndex(e => e.url === entry.url) === index) + // place OK responses first + .sort((a, b) => { + if (a.isOk() === b.isOk()) { + return 0; + } else if (a.isOk()) { + return -1; + } else { + return 1; + } + }) + // place a separator between OK and Error responses + .map((entry, index, array) => { + const previous = array[index - 1]; + const options: QuickOpenGroupItemOptions = {}; + if (previous && previous.isOk() && !entry.isOk()) { + options.showBorder = true; + } + return this.convertEntryToQuickOpenItem(entry, options); + }); + + items.push(...sortedEntries); + acceptor(items); + } + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(ElectronRemoteCommands.CONNECT_TO_REMOTE, { + execute: () => { + this.historyEntries = this.computeHistoryCache(); + this.quickOpenService.open(this, { + placeholder: 'Type the URL to connect to...', + fuzzyMatchLabel: true, + }); + } + }); + + registry.registerCommand(ElectronRemoteCommands.DISCONNECT_FROM_REMOTE, { + isEnabled: () => ElectronBackendLocation.isRemote(), + isVisible: () => ElectronBackendLocation.isRemote(), + execute: () => { + this.windowService.openNewWindow('localhost'); + close(); + }, + }); + registry.registerCommand(ElectronRemoteCommands.CLEAR_REMOTE_HISTORY, { + execute: () => this.clearHistory() + }); + } + + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybindings({ + command: ElectronRemoteCommands.CONNECT_TO_REMOTE.id, + keybinding: "ctrl+alt+r" + }); + } + + registerMenus(registry: MenuModelRegistry) { + registry.registerMenuAction(ElectronMenus.ELECTRON_REMOTE, { + commandId: ElectronRemoteCommands.CONNECT_TO_REMOTE.id, + order: 'z4', + }); + // Do not load the disconnect button if we are not on a remote server + if (ElectronBackendLocation.isRemote()) { + registry.registerMenuAction(ElectronMenus.ELECTRON_REMOTE, { + commandId: ElectronRemoteCommands.DISCONNECT_FROM_REMOTE.id, + order: 'z5', + }); + } + } +} + +export namespace ElectronRemoteCommands { + export const CONNECT_TO_REMOTE: Command = { + id: 'electron.remote.connect', + label: 'Remote: Connect to a Server' + }; + export const CLEAR_REMOTE_HISTORY: Command = { + id: 'electron.remote.history.clear', + label: 'Remote: Clear host history' + }; + export const DISCONNECT_FROM_REMOTE: Command = { + id: 'electron.remote.disconnect', + label: 'Remote: Disconnect', + }; +} + +export namespace ElectronMenus { + export const ELECTRON_REMOTE = [...CommonMenus.FILE_OPEN, 'z_connect']; +} + +export namespace ElectronRemoteHistory { + export const KEY = 'theia.remote.history'; +} diff --git a/packages/core/src/electron-browser/remote/electron-remote-module.ts b/packages/core/src/electron-browser/remote/electron-remote-module.ts new file mode 100644 index 0000000000000..c4e7e5fa50d68 --- /dev/null +++ b/packages/core/src/electron-browser/remote/electron-remote-module.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { CommandContribution, MenuContribution } from "../../common"; +import { + ElectronRemoteQuestionPath, ElectronRemoteAnswer, + defaultElectronRemoteQuestionPath, defaultElectronRemoteAnswer +} from '../../common/remote/electron-remote-protocol'; +import { KeybindingContribution } from '../../browser'; +import { ElectronRemoteContribution } from './electron-remote-contribution'; + +export default new ContainerModule(bind => { + bind(ElectronRemoteContribution).toSelf().inSingletonScope(); + for (const serviceIdentifier of [KeybindingContribution, CommandContribution, MenuContribution]) { + bind(serviceIdentifier).toService(ElectronRemoteContribution); + } + bind(ElectronRemoteQuestionPath).toConstantValue(defaultElectronRemoteQuestionPath); + bind(ElectronRemoteAnswer).toConstantValue(defaultElectronRemoteAnswer); +}); diff --git a/packages/core/src/electron-node/remote/electron-remote-backend-contribution.ts b/packages/core/src/electron-node/remote/electron-remote-backend-contribution.ts new file mode 100644 index 0000000000000..72c6a2c4fd488 --- /dev/null +++ b/packages/core/src/electron-node/remote/electron-remote-backend-contribution.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as express from 'express'; +import { injectable, inject } from 'inversify'; +import { BackendApplicationContribution } from '../../node'; +import { ElectronRemoteQuestionPath, ElectronRemoteAnswer } from '../../common/remote/electron-remote-protocol'; + +@injectable() +export class ElectronRemoteBackendContribution implements BackendApplicationContribution { + + @inject(ElectronRemoteQuestionPath) protected readonly path: string; + @inject(ElectronRemoteAnswer) protected readonly answer: object; + + configure(app: express.Application): void { + app.get(this.path, (request, response) => { + response.send(this.answer); + }); + } +} diff --git a/packages/core/src/electron-node/remote/electron-remote-module.ts b/packages/core/src/electron-node/remote/electron-remote-module.ts new file mode 100644 index 0000000000000..0e576271d3c56 --- /dev/null +++ b/packages/core/src/electron-node/remote/electron-remote-module.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (C) 2018 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule } from 'inversify'; +import { BackendApplicationContribution } from '../../node'; +import { ElectronRemoteBackendContribution } from './electron-remote-backend-contribution'; +import { + ElectronRemoteQuestionPath, ElectronRemoteAnswer, + defaultElectronRemoteQuestionPath, defaultElectronRemoteAnswer +} from '../../common/remote/electron-remote-protocol'; + +export default new ContainerModule(bind => { + bind(BackendApplicationContribution).to(ElectronRemoteBackendContribution).inSingletonScope(); + bind(ElectronRemoteQuestionPath).toConstantValue(defaultElectronRemoteQuestionPath); + bind(ElectronRemoteAnswer).toConstantValue(defaultElectronRemoteAnswer); +}); diff --git a/packages/monaco/src/browser/monaco-loader.ts b/packages/monaco/src/browser/monaco-loader.ts index 95ffa6fa84308..ce138399094c3 100644 --- a/packages/monaco/src/browser/monaco-loader.ts +++ b/packages/monaco/src/browser/monaco-loader.ts @@ -34,7 +34,7 @@ export function loadVsRequire(context: any): Promise { context.require = originalRequire; } resolve(amdRequire); - }); + }, { once: true }); document.body.appendChild(vsLoader); }, { once: true }) ); diff --git a/packages/monaco/src/electron-browser/monaco-electron-module.ts b/packages/monaco/src/electron-browser/monaco-electron-module.ts index 4ac496d0fbbcc..5398e4f4f9636 100644 --- a/packages/monaco/src/electron-browser/monaco-electron-module.ts +++ b/packages/monaco/src/electron-browser/monaco-electron-module.ts @@ -20,7 +20,8 @@ import { loadVsRequire, loadMonaco } from "../browser/monaco-loader"; export { ContainerModule }; -const s = self; +// tslint:disable-next-line:no-any +const globals = self; /** * We cannot use `FileUri#create` because URIs with file scheme cannot be properly decoded via the AMD loader. @@ -37,13 +38,22 @@ const uriFromPath = (filePath: string) => { export default loadVsRequire(global) .then(vsRequire => { - const baseUrl = uriFromPath(__dirname); + const isRemote = !/^file:/.test(self.location.href); + const baseUrl = isRemote ? self.location.href : uriFromPath(__dirname); vsRequire.config({ baseUrl }); // workaround monaco-css not understanding the environment - s.module = undefined; + globals.module = undefined; // workaround monaco-typescript not understanding the environment - s.process.browser = true; + globals.process.browser = true; + + // vscode-loader patching: https://github.com/Microsoft/vscode-loader/issues/12 + if (isRemote) { + Object.defineProperty(globals.AMDLoader.Environment.prototype, 'isNode', { + get: () => false + }); + } + return loadMonaco(vsRequire); }) .then(() => import('../browser/monaco-frontend-module')) diff --git a/yarn.lock b/yarn.lock index 70a39014e5067..ad2942f2f0a70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -439,30 +439,64 @@ debug "^3.1.0" mamacro "^0.0.3" +"@webassemblyjs/ast@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25" + dependencies: + "@webassemblyjs/helper-module-context" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/wast-parser" "1.5.13" + debug "^3.1.0" + mamacro "^0.0.3" + "@webassemblyjs/floating-point-hex-parser@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.12.tgz#0f36044ffe9652468ce7ae5a08716a4eeff9cd9c" +"@webassemblyjs/floating-point-hex-parser@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz#29ce0baa97411f70e8cce68ce9c0f9d819a4e298" + "@webassemblyjs/helper-api-error@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.12.tgz#05466833ff2f9d8953a1a327746e1d112ea62aaf" +"@webassemblyjs/helper-api-error@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz#e49b051d67ee19a56e29b9aa8bd949b5b4442a59" + "@webassemblyjs/helper-buffer@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.12.tgz#1f0de5aaabefef89aec314f7f970009cd159c73d" dependencies: debug "^3.1.0" +"@webassemblyjs/helper-buffer@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz#873bb0a1b46449231137c1262ddfd05695195a1e" + dependencies: + debug "^3.1.0" + "@webassemblyjs/helper-code-frame@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.12.tgz#3cdc1953093760d1c0f0caf745ccd62bdb6627c7" dependencies: "@webassemblyjs/wast-printer" "1.5.12" +"@webassemblyjs/helper-code-frame@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz#1bd2181b6a0be14e004f0fe9f5a660d265362b58" + dependencies: + "@webassemblyjs/wast-printer" "1.5.13" + "@webassemblyjs/helper-fsm@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.12.tgz#6bc1442b037f8e30f2e57b987cee5c806dd15027" +"@webassemblyjs/helper-fsm@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz#cdf3d9d33005d543a5c5e5adaabf679ffa8db924" + "@webassemblyjs/helper-module-context@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.12.tgz#b5588ca78b33b8a0da75f9ab8c769a3707baa861" @@ -470,10 +504,21 @@ debug "^3.1.0" mamacro "^0.0.3" +"@webassemblyjs/helper-module-context@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz#dc29ddfb51ed657655286f94a5d72d8a489147c5" + dependencies: + debug "^3.1.0" + mamacro "^0.0.3" + "@webassemblyjs/helper-wasm-bytecode@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.12.tgz#d12a3859db882a448891a866a05d0be63785b616" +"@webassemblyjs/helper-wasm-bytecode@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz#03245817f0a762382e61733146f5773def15a747" + "@webassemblyjs/helper-wasm-section@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.12.tgz#ff9fe1507d368ad437e7969d25e8c1693dac1884" @@ -484,22 +529,48 @@ "@webassemblyjs/wasm-gen" "1.5.12" debug "^3.1.0" +"@webassemblyjs/helper-wasm-section@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz#efc76f44a10d3073b584b43c38a179df173d5c7d" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-buffer" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/wasm-gen" "1.5.13" + debug "^3.1.0" + "@webassemblyjs/ieee754@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.5.12.tgz#ee9574bc558888f13097ce3e7900dff234ea19a4" dependencies: ieee754 "^1.1.11" +"@webassemblyjs/ieee754@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz#573e97c8c12e4eebb316ca5fde0203ddd90b0364" + dependencies: + ieee754 "^1.1.11" + "@webassemblyjs/leb128@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.5.12.tgz#0308eec652765ee567d8a5fa108b4f0b25b458e1" dependencies: leb "^0.3.0" +"@webassemblyjs/leb128@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.5.13.tgz#ab52ebab9cec283c1c1897ac1da833a04a3f4cee" + dependencies: + long "4.0.0" + "@webassemblyjs/utf8@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.5.12.tgz#d5916222ef314bf60d6806ed5ac045989bfd92ce" +"@webassemblyjs/utf8@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.5.13.tgz#6b53d2cd861cf94fa99c1f12779dde692fbc2469" + "@webassemblyjs/wasm-edit@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.12.tgz#821c9358e644a166f2c910e5af1b46ce795a17aa" @@ -514,6 +585,20 @@ "@webassemblyjs/wast-printer" "1.5.12" debug "^3.1.0" +"@webassemblyjs/wasm-edit@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz#c9cef5664c245cf11b3b3a73110c9155831724a8" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-buffer" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/helper-wasm-section" "1.5.13" + "@webassemblyjs/wasm-gen" "1.5.13" + "@webassemblyjs/wasm-opt" "1.5.13" + "@webassemblyjs/wasm-parser" "1.5.13" + "@webassemblyjs/wast-printer" "1.5.13" + debug "^3.1.0" + "@webassemblyjs/wasm-gen@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.12.tgz#0b7ccfdb93dab902cc0251014e2e18bae3139bcb" @@ -524,6 +609,16 @@ "@webassemblyjs/leb128" "1.5.12" "@webassemblyjs/utf8" "1.5.12" +"@webassemblyjs/wasm-gen@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz#8e6ea113c4b432fa66540189e79b16d7a140700e" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/ieee754" "1.5.13" + "@webassemblyjs/leb128" "1.5.13" + "@webassemblyjs/utf8" "1.5.13" + "@webassemblyjs/wasm-opt@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.12.tgz#bd758a8bc670f585ff1ae85f84095a9e0229cbc9" @@ -534,6 +629,16 @@ "@webassemblyjs/wasm-parser" "1.5.12" debug "^3.1.0" +"@webassemblyjs/wasm-opt@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz#147aad7717a7ee4211c36b21a5f4c30dddf33138" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-buffer" "1.5.13" + "@webassemblyjs/wasm-gen" "1.5.13" + "@webassemblyjs/wasm-parser" "1.5.13" + debug "^3.1.0" + "@webassemblyjs/wasm-parser@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.12.tgz#7b10b4388ecf98bd7a22e702aa62ec2f46d0c75e" @@ -545,6 +650,17 @@ "@webassemblyjs/leb128" "1.5.12" "@webassemblyjs/utf8" "1.5.12" +"@webassemblyjs/wasm-parser@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz#6f46516c5bb23904fbdf58009233c2dd8a54c72f" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-api-error" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/ieee754" "1.5.13" + "@webassemblyjs/leb128" "1.5.13" + "@webassemblyjs/utf8" "1.5.13" + "@webassemblyjs/wast-parser@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.5.12.tgz#9cf5ae600ecae0640437b5d4de5dd6b6088d0d8b" @@ -557,6 +673,18 @@ long "^3.2.0" mamacro "^0.0.3" +"@webassemblyjs/wast-parser@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz#5727a705d397ae6a3ae99d7f5460acf2ec646eea" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/floating-point-hex-parser" "1.5.13" + "@webassemblyjs/helper-api-error" "1.5.13" + "@webassemblyjs/helper-code-frame" "1.5.13" + "@webassemblyjs/helper-fsm" "1.5.13" + long "^3.2.0" + mamacro "^0.0.3" + "@webassemblyjs/wast-printer@1.5.12": version "1.5.12" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.5.12.tgz#563ca4d01b22d21640b2463dc5e3d7f7d9dac520" @@ -565,6 +693,14 @@ "@webassemblyjs/wast-parser" "1.5.12" long "^3.2.0" +"@webassemblyjs/wast-printer@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz#bb34d528c14b4f579e7ec11e793ec50ad7cd7c95" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/wast-parser" "1.5.13" + long "^3.2.0" + JSONStream@^1.0.4: version "1.3.3" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf" @@ -927,6 +1063,13 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.7.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" +axios@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" + dependencies: + follow-redirects "^1.3.0" + is-buffer "^1.1.5" + babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -2042,6 +2185,10 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +chardet@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029" + check-error@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -3443,6 +3590,14 @@ enhanced-resolve@^4.0.0: memory-fs "^0.4.0" tapable "^1.0.0" +enhanced-resolve@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + tapable "^1.0.0" + entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" @@ -3700,6 +3855,14 @@ external-editor@^2.0.4, external-editor@^2.1.0: iconv-lite "^0.4.17" tmp "^0.0.33" +external-editor@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.0.tgz#dc35c48c6f98a30ca27a20e9687d7f3c77704bb6" + dependencies: + chardet "^0.5.0" + iconv-lite "^0.4.22" + tmp "^0.0.33" + extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -3952,6 +4115,12 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +follow-redirects@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.1.tgz#67a8f14f5a1f67f962c2c46469c79eaec0a90291" + dependencies: + debug "^3.1.0" + font-awesome-webpack@0.0.5-beta.2: version "0.0.5-beta.2" resolved "https://registry.yarnpkg.com/font-awesome-webpack/-/font-awesome-webpack-0.0.5-beta.2.tgz#9ea5f22f0615d08e76d8db341563649a726286d6" @@ -4319,6 +4488,10 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" +global-modules-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.1.0.tgz#923ec524e8726bb0c1a4ed4b8e21e1ff80c88bbb" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -4777,7 +4950,7 @@ iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: @@ -4823,6 +4996,13 @@ import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" +import-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -4901,7 +5081,25 @@ inquirer@^5.1.0, inquirer@^5.2.0: strip-ansi "^4.0.0" through "^2.3.6" -interpret@^1.0.0, interpret@^1.0.4: +inquirer@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.0.0.tgz#e8c20303ddc15bbfc2c12a6213710ccd9e1413d8" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.0" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.1.0" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +interpret@^1.0.0, interpret@^1.0.4, interpret@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" @@ -6049,6 +6247,10 @@ lolex@^2.1.2, lolex@^2.3.2: version "2.7.0" resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.0.tgz#9c087a69ec440e39d3f796767cf1b2cdc43d5ea5" +long@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + long@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" @@ -8412,6 +8614,12 @@ rxjs@^5.0.0-beta.11, rxjs@^5.1.1, rxjs@^5.4.2, rxjs@^5.5.2: dependencies: symbol-observable "1.0.1" +rxjs@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1" + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -9816,6 +10024,10 @@ v8-compile-cache@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4" +v8-compile-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.0.tgz#526492e35fc616864284700b7043e01baee09f0a" + valid-filename@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/valid-filename/-/valid-filename-2.0.1.tgz#0768d6f364b1ed3bdf68f0d15abffb0d9d6cecaf" @@ -10096,6 +10308,22 @@ webpack-cli@2.0.12: yeoman-environment "^2.0.0" yeoman-generator "^2.0.3" +webpack-cli@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.0.8.tgz#90eddcf04a4bfc31aa8c0edc4c76785bc4f1ccd9" + dependencies: + chalk "^2.4.1" + cross-spawn "^6.0.5" + enhanced-resolve "^4.0.0" + global-modules-path "^2.1.0" + import-local "^1.0.0" + inquirer "^6.0.0" + interpret "^1.1.0" + loader-utils "^1.1.0" + supports-color "^5.4.0" + v8-compile-cache "^2.0.0" + yargs "^11.1.0" + webpack-sources@^1.0.1, webpack-sources@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" @@ -10133,6 +10361,36 @@ webpack@^4.0.0: watchpack "^1.5.0" webpack-sources "^1.0.1" +webpack@^4.13.0: + version "4.15.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.15.0.tgz#c9704e98f045499b84bdb194d23ade18dfaf4441" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-module-context" "1.5.13" + "@webassemblyjs/wasm-edit" "1.5.13" + "@webassemblyjs/wasm-opt" "1.5.13" + "@webassemblyjs/wasm-parser" "1.5.13" + acorn "^5.6.2" + acorn-dynamic-import "^3.0.0" + ajv "^6.1.0" + ajv-keywords "^3.1.0" + chrome-trace-event "^1.0.0" + enhanced-resolve "^4.1.0" + eslint-scope "^3.7.1" + json-parse-better-errors "^1.0.2" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + micromatch "^3.1.8" + mkdirp "~0.5.0" + neo-async "^2.5.0" + node-libs-browser "^2.0.0" + schema-utils "^0.4.4" + tapable "^1.0.0" + uglifyjs-webpack-plugin "^1.2.4" + watchpack "^1.5.0" + webpack-sources "^1.0.1" + wgxpath@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wgxpath/-/wgxpath-1.0.0.tgz#eef8a4b9d558cc495ad3a9a2b751597ecd9af690" @@ -10381,7 +10639,7 @@ yargs-parser@^9.0.2: dependencies: camelcase "^4.1.0" -yargs@11.1.0: +yargs@11.1.0, yargs@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" dependencies: