UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

300 lines • 14 kB
"use strict"; // ***************************************************************************** // 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-only WITH Classpath-exception-2.0 // ***************************************************************************** var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BackendApplication = exports.BackendApplicationCliContribution = exports.BackendApplicationContribution = exports.BackendApplicationServer = void 0; const dns = require("dns"); const path = require("path"); const http = require("http"); const https = require("https"); const express = require("express"); const fs = require("fs-extra"); const inversify_1 = require("inversify"); const common_1 = require("../common"); const promise_util_1 = require("../common/promise-util"); const index_1 = require("../common/index"); const application_package_1 = require("@theia/application-package"); const process_utils_1 = require("./process-utils"); const APP_PROJECT_PATH = 'app-project-path'; const TIMER_WARNING_THRESHOLD = 50; const DEFAULT_PORT = index_1.environment.electron.is() ? 0 : 3000; const DEFAULT_HOST = 'localhost'; const DEFAULT_SSL = false; const DEFAULT_DNS_DEFAULT_RESULT_ORDER = 'ipv4first'; exports.BackendApplicationServer = Symbol('BackendApplicationServer'); exports.BackendApplicationContribution = Symbol('BackendApplicationContribution'); let BackendApplicationCliContribution = class BackendApplicationCliContribution { constructor() { this.dnsDefaultResultOrder = DEFAULT_DNS_DEFAULT_RESULT_ORDER; } configure(conf) { conf.option('port', { alias: 'p', description: 'The port the backend server listens on.', type: 'number', default: DEFAULT_PORT }); conf.option('hostname', { alias: 'h', description: 'The allowed hostname for connections.', type: 'string', default: DEFAULT_HOST }); conf.option('ssl', { description: 'Use SSL (HTTPS), cert and certkey must also be set', type: 'boolean', default: DEFAULT_SSL }); conf.option('cert', { description: 'Path to SSL certificate.', type: 'string' }); conf.option('certkey', { description: 'Path to SSL certificate key.', type: 'string' }); conf.option(APP_PROJECT_PATH, { description: 'Sets the application project directory', default: this.appProjectPath() }); conf.option('dnsDefaultResultOrder', { type: 'string', description: 'Configure Node\'s DNS resolver default behavior, see https://nodejs.org/docs/latest-v18.x/api/dns.html#dnssetdefaultresultorderorder', choices: ['ipv4first', 'verbatim', 'nodeDefault'], default: DEFAULT_DNS_DEFAULT_RESULT_ORDER }); } setArguments(args) { this.port = args.port; this.hostname = args.hostname; this.ssl = args.ssl; this.cert = args.cert; this.certkey = args.certkey; this.projectPath = args[APP_PROJECT_PATH]; this.dnsDefaultResultOrder = args.dnsDefaultResultOrder; } appProjectPath() { if (index_1.environment.electron.is()) { if (process.env.THEIA_APP_PROJECT_PATH) { return process.env.THEIA_APP_PROJECT_PATH; } throw new Error('The \'THEIA_APP_PROJECT_PATH\' environment variable must be set when running in electron.'); } return process.cwd(); } }; BackendApplicationCliContribution = __decorate([ (0, inversify_1.injectable)() ], BackendApplicationCliContribution); exports.BackendApplicationCliContribution = BackendApplicationCliContribution; /** * The main entry point for Theia applications. */ let BackendApplication = class BackendApplication { constructor(contributionsProvider, cliParams) { this.contributionsProvider = contributionsProvider; this.cliParams = cliParams; this.app = express(); process.on('uncaughtException', error => { this.handleUncaughtError(error); }); // Workaround for Electron not installing a handler to ignore SIGPIPE error // (https://github.com/electron/electron/issues/13254) process.on('SIGPIPE', () => { console.error(new Error('Unexpected SIGPIPE')); }); /** * Kill the current process tree on exit. */ function signalHandler(signal) { process.exit(1); } // Handles normal process termination. process.on('exit', () => this.onStop()); // Handles `Ctrl+C`. process.on('SIGINT', signalHandler); // Handles `kill pid`. process.on('SIGTERM', signalHandler); } async initialize() { for (const contribution of this.contributionsProvider.getContributions()) { if (contribution.initialize) { try { await this.measure(contribution.constructor.name + '.initialize', () => contribution.initialize()); } catch (error) { console.error('Could not initialize contribution', error); } } } } init() { this.configure(); } async configure() { // Do not await the initialization because contributions are expected to handle // concurrent initialize/configure in undefined order if they provide both this.initialize(); this.app.get('*.js', this.serveGzipped.bind(this, 'text/javascript')); this.app.get('*.js.map', this.serveGzipped.bind(this, 'application/json')); this.app.get('*.css', this.serveGzipped.bind(this, 'text/css')); this.app.get('*.wasm', this.serveGzipped.bind(this, 'application/wasm')); this.app.get('*.gif', this.serveGzipped.bind(this, 'image/gif')); this.app.get('*.png', this.serveGzipped.bind(this, 'image/png')); this.app.get('*.svg', this.serveGzipped.bind(this, 'image/svg+xml')); this.app.get('*.eot', this.serveGzipped.bind(this, 'application/vnd.ms-fontobject')); this.app.get('*.ttf', this.serveGzipped.bind(this, 'font/ttf')); this.app.get('*.woff', this.serveGzipped.bind(this, 'font/woff')); this.app.get('*.woff2', this.serveGzipped.bind(this, 'font/woff2')); for (const contribution of this.contributionsProvider.getContributions()) { if (contribution.configure) { try { await this.measure(contribution.constructor.name + '.configure', () => contribution.configure(this.app)); } catch (error) { console.error('Could not configure contribution', error); } } } } use(...handlers) { this.app.use(...handlers); } async start(port, hostname) { hostname !== null && hostname !== void 0 ? hostname : (hostname = this.cliParams.hostname); port !== null && port !== void 0 ? port : (port = this.cliParams.port); if (this.cliParams.dnsDefaultResultOrder !== 'nodeDefault') { dns.setDefaultResultOrder(this.cliParams.dnsDefaultResultOrder); } const deferred = new promise_util_1.Deferred(); let server; if (this.cliParams.ssl) { if (this.cliParams.cert === undefined) { throw new Error('Missing --cert option, see --help for usage'); } if (this.cliParams.certkey === undefined) { throw new Error('Missing --certkey option, see --help for usage'); } let key; let cert; try { key = await fs.readFile(this.cliParams.certkey); } catch (err) { console.error("Can't read certificate key"); throw err; } try { cert = await fs.readFile(this.cliParams.cert); } catch (err) { console.error("Can't read certificate"); throw err; } server = https.createServer({ key, cert }, this.app); } else { server = http.createServer(this.app); } server.on('error', error => { deferred.reject(error); /* The backend might run in a separate process, * so we defer `process.exit` to let time for logging in the parent process */ setTimeout(process.exit, 0, 1); }); server.listen(port, hostname, () => { // address should be defined at this point const address = server.address(); const url = typeof address === 'string' ? address : this.getHttpUrl(address, this.cliParams.ssl); console.info(`Theia app listening on ${url}.`); deferred.resolve(server); }); /* Allow any number of websocket servers. */ server.setMaxListeners(0); for (const contribution of this.contributionsProvider.getContributions()) { if (contribution.onStart) { try { await this.measure(contribution.constructor.name + '.onStart', () => contribution.onStart(server)); } catch (error) { console.error('Could not start contribution', error); } } } return this.stopwatch.startAsync('server', 'Finished starting backend application', () => deferred.promise); } getHttpUrl({ address, port, family }, ssl) { const scheme = ssl ? 'https' : 'http'; return family.toLowerCase() === 'ipv6' ? `${scheme}://[${address}]:${port}` : `${scheme}://${address}:${port}`; } onStop() { console.info('>>> Stopping backend contributions...'); for (const contrib of this.contributionsProvider.getContributions()) { if (contrib.onStop) { try { contrib.onStop(this.app); } catch (error) { console.error('Could not stop contribution', error); } } } console.info('<<< All backend contributions have been stopped.'); this.processUtils.terminateProcessTree(process.pid); } async serveGzipped(contentType, req, res, next) { const acceptedEncodings = req.acceptsEncodings(); const gzUrl = `${req.url}.gz`; const gzPath = path.join(this.applicationPackage.projectPath, 'lib', 'frontend', gzUrl); if (acceptedEncodings.indexOf('gzip') === -1 || !(await fs.pathExists(gzPath))) { next(); return; } req.url = gzUrl; res.set('Content-Encoding', 'gzip'); res.set('Content-Type', contentType); next(); } async measure(name, fn) { return this.stopwatch.startAsync(name, `Backend ${name}`, fn, { thresholdMillis: TIMER_WARNING_THRESHOLD }); } handleUncaughtError(error) { if (error) { console.error('Uncaught Exception: ', error.toString()); if (error.stack) { console.error(error.stack); } } } }; __decorate([ (0, inversify_1.inject)(application_package_1.ApplicationPackage), __metadata("design:type", application_package_1.ApplicationPackage) ], BackendApplication.prototype, "applicationPackage", void 0); __decorate([ (0, inversify_1.inject)(process_utils_1.ProcessUtils), __metadata("design:type", process_utils_1.ProcessUtils) ], BackendApplication.prototype, "processUtils", void 0); __decorate([ (0, inversify_1.inject)(common_1.Stopwatch), __metadata("design:type", common_1.Stopwatch) ], BackendApplication.prototype, "stopwatch", void 0); __decorate([ (0, inversify_1.postConstruct)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], BackendApplication.prototype, "init", null); BackendApplication = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)(common_1.ContributionProvider)), __param(0, (0, inversify_1.named)(exports.BackendApplicationContribution)), __param(1, (0, inversify_1.inject)(BackendApplicationCliContribution)), __metadata("design:paramtypes", [Object, BackendApplicationCliContribution]) ], BackendApplication); exports.BackendApplication = BackendApplication; //# sourceMappingURL=backend-application.js.map