@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
270 lines • 12.5 kB
JavaScript
"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 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 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;
exports.BackendApplicationServer = Symbol('BackendApplicationServer');
exports.BackendApplicationContribution = Symbol('BackendApplicationContribution');
let BackendApplicationCliContribution = class BackendApplicationCliContribution {
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() });
}
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];
}
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);
}
}
}
}
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'));
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(aPort, aHostname) {
const hostname = aHostname !== undefined ? aHostname : this.cliParams.hostname;
const port = aPort !== undefined ? aPort : this.cliParams.port;
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, () => {
const scheme = this.cliParams.ssl ? 'https' : 'http';
console.info(`Theia app listening on ${scheme}://${hostname || 'localhost'}:${server.address().port}.`);
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);
}
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', 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", Promise)
], BackendApplication.prototype, "configure", 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