1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
16 | return new (P || (P = Promise))(function (resolve, reject) {
|
17 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
18 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
19 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
20 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
21 | });
|
22 | };
|
23 | Object.defineProperty(exports, "__esModule", { value: true });
|
24 | const assert = require("assert");
|
25 | const escapeHtml = require("escape-html");
|
26 | const express = require("express");
|
27 | const fs = require("mz/fs");
|
28 | const path = require("path");
|
29 | const path_transformers_1 = require("polymer-build/lib/path-transformers");
|
30 | const send = require("send");
|
31 |
|
32 |
|
33 | const http = require("spdy");
|
34 | const compile_middleware_1 = require("./compile-middleware");
|
35 | const config_1 = require("./config");
|
36 | const custom_elements_es5_adapter_middleware_1 = require("./custom-elements-es5-adapter-middleware");
|
37 | const make_app_1 = require("./make_app");
|
38 | const open_browser_1 = require("./util/open_browser");
|
39 | const push_1 = require("./util/push");
|
40 | const tls_1 = require("./util/tls");
|
41 | const compression = require("compression");
|
42 | const cors = require("cors");
|
43 | const httpProxy = require('http-proxy-middleware');
|
44 | function applyDefaultServerOptions(options) {
|
45 | const withDefaults = Object.assign({}, options);
|
46 | Object.assign(withDefaults, {
|
47 | port: options.port || 0,
|
48 | hostname: options.hostname || 'localhost',
|
49 | root: path.resolve(options.root || '.'),
|
50 | compile: options.compile || 'auto',
|
51 | certPath: options.certPath || 'cert.pem',
|
52 | keyPath: options.keyPath || 'key.pem',
|
53 | componentDir: config_1.getComponentDir(options),
|
54 | componentUrl: options.componentUrl || 'components'
|
55 | });
|
56 | withDefaults.packageName = config_1.getPackageName(withDefaults);
|
57 | return withDefaults;
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | function startServer(options, appMapper) {
|
72 | return __awaiter(this, void 0, void 0, function* () {
|
73 | return (yield _startServer(options, appMapper)).server;
|
74 | });
|
75 | }
|
76 | exports.startServer = startServer;
|
77 | function _startServer(options, appMapper) {
|
78 | return __awaiter(this, void 0, void 0, function* () {
|
79 | options = options || {};
|
80 | assertNodeVersion(options);
|
81 | try {
|
82 | let app = getApp(options);
|
83 | if (appMapper) {
|
84 |
|
85 |
|
86 | app = (yield appMapper(app, options)) || app;
|
87 | }
|
88 | const server = yield startWithApp(options, app);
|
89 | return { app, server };
|
90 | }
|
91 | catch (e) {
|
92 | console.error('ERROR: Server failed to start:', e);
|
93 | throw new Error(e);
|
94 | }
|
95 | });
|
96 | }
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | function startServers(options, appMapper) {
|
102 | return __awaiter(this, void 0, void 0, function* () {
|
103 | options = applyDefaultServerOptions(options);
|
104 | const variants = yield findVariants(options);
|
105 |
|
106 |
|
107 | if (variants.length > 0) {
|
108 | return yield startVariants(options, variants, appMapper);
|
109 | }
|
110 | const serverAndApp = yield _startServer(options, appMapper);
|
111 | return {
|
112 | options,
|
113 | kind: 'mainline',
|
114 | server: serverAndApp.server,
|
115 | app: serverAndApp.app,
|
116 | };
|
117 | });
|
118 | }
|
119 | exports.startServers = startServers;
|
120 |
|
121 |
|
122 | function findVariants(options) {
|
123 | return __awaiter(this, void 0, void 0, function* () {
|
124 | const root = options.root || process.cwd();
|
125 | const filesInRoot = yield fs.readdir(root);
|
126 | const variants = filesInRoot
|
127 | .map((f) => {
|
128 | const match = f.match(`^${options.componentDir}-(.*)`);
|
129 | return match && { name: match[1], directory: match[0] };
|
130 | })
|
131 | .filter((f) => f != null && f.name !== '');
|
132 | return variants;
|
133 | });
|
134 | }
|
135 | function startVariants(options, variants, appMapper) {
|
136 | return __awaiter(this, void 0, void 0, function* () {
|
137 | const mainlineOptions = Object.assign({}, options);
|
138 | mainlineOptions.port = 0;
|
139 | const mainServer = yield _startServer(mainlineOptions, appMapper);
|
140 | const mainServerInfo = {
|
141 | kind: 'mainline',
|
142 | server: mainServer.server,
|
143 | app: mainServer.app,
|
144 | options: mainlineOptions,
|
145 | };
|
146 | const variantServerInfos = [];
|
147 | for (const variant of variants) {
|
148 | const variantOpts = Object.assign({}, options);
|
149 | variantOpts.port = 0;
|
150 | variantOpts.componentDir = variant.directory;
|
151 | const variantServer = yield _startServer(variantOpts, appMapper);
|
152 | variantServerInfos.push({
|
153 | kind: 'variant',
|
154 | variantName: variant.name,
|
155 | dependencyDir: variant.directory,
|
156 | server: variantServer.server,
|
157 | app: variantServer.app,
|
158 | options: variantOpts
|
159 | });
|
160 | }
|
161 | const controlServerInfo = yield startControlServer(options, mainServerInfo, variantServerInfos);
|
162 | const servers = [controlServerInfo, mainServerInfo]
|
163 | .concat(variantServerInfos);
|
164 | const result = {
|
165 | kind: 'MultipleServers',
|
166 | control: controlServerInfo,
|
167 | mainline: mainServerInfo,
|
168 | variants: variantServerInfos,
|
169 | servers,
|
170 | };
|
171 | return result;
|
172 | });
|
173 | }
|
174 | function startControlServer(options, mainlineInfo, variantInfos) {
|
175 | return __awaiter(this, void 0, void 0, function* () {
|
176 | options = applyDefaultServerOptions(options);
|
177 | const app = express();
|
178 | app.get('/api/serverInfo', (_req, res) => {
|
179 | res.contentType('json');
|
180 | res.send(JSON.stringify({
|
181 | packageName: options.packageName,
|
182 | mainlineServer: {
|
183 | port: assertNotString(mainlineInfo.server.address()).port,
|
184 | },
|
185 | variants: variantInfos.map((info) => {
|
186 | return {
|
187 | name: info.variantName,
|
188 | port: assertNotString(info.server.address()).port,
|
189 | };
|
190 | })
|
191 | }));
|
192 | res.end();
|
193 | });
|
194 | const indexPath = path.join(__dirname, '..', 'static', 'index.html');
|
195 | app.get('/', (_req, res) => __awaiter(this, void 0, void 0, function* () {
|
196 | res.contentType('html');
|
197 | const indexContents = yield fs.readFile(indexPath, 'utf-8');
|
198 | res.send(indexContents);
|
199 | res.end();
|
200 | }));
|
201 | const controlServer = {
|
202 | kind: 'control',
|
203 | options: options,
|
204 | server: yield startWithApp(options, app),
|
205 | app
|
206 | };
|
207 | return controlServer;
|
208 | });
|
209 | }
|
210 | exports.startControlServer = startControlServer;
|
211 | function getApp(options) {
|
212 | options = applyDefaultServerOptions(options);
|
213 |
|
214 | if (options.pushManifestPath) {
|
215 | push_1.getPushManifest(options.root, options.pushManifestPath);
|
216 | }
|
217 | const root = options.root || '.';
|
218 | const app = express();
|
219 | app.use(compression());
|
220 | if (options.additionalRoutes) {
|
221 | options.additionalRoutes.forEach((handler, route) => {
|
222 | app.get(route, handler);
|
223 | });
|
224 | }
|
225 | const componentUrl = options.componentUrl;
|
226 | const polyserve = make_app_1.makeApp({
|
227 | componentDir: options.componentDir,
|
228 | packageName: options.packageName,
|
229 | root: root,
|
230 | headers: options.headers,
|
231 | });
|
232 | const filePathRegex = /.*\/.+\..{1,}$/;
|
233 | if (options.proxy) {
|
234 | if (options.proxy.path.startsWith(componentUrl)) {
|
235 | console.error(`proxy path can not start with ${componentUrl}.`);
|
236 | return;
|
237 | }
|
238 | let escapedPath = options.proxy.path;
|
239 | for (const char of ['*', '?', '+']) {
|
240 | if (escapedPath.indexOf(char) > -1) {
|
241 | console.warn(`Proxy path includes character "${char}"` +
|
242 | `which can cause problems during route matching.`);
|
243 | }
|
244 | }
|
245 | if (escapedPath.startsWith('/')) {
|
246 | escapedPath = escapedPath.substring(1);
|
247 | }
|
248 | if (escapedPath.endsWith('/')) {
|
249 | escapedPath = escapedPath.slice(0, -1);
|
250 | }
|
251 | const pathRewrite = {};
|
252 | pathRewrite[`^/${escapedPath}`] = '';
|
253 | const apiProxy = httpProxy(`/${escapedPath}`, {
|
254 | target: options.proxy.target,
|
255 | changeOrigin: true,
|
256 | pathRewrite: pathRewrite,
|
257 | logLevel: 'warn',
|
258 | });
|
259 | app.use(`/${escapedPath}/`, apiProxy);
|
260 | }
|
261 | app.use('*', custom_elements_es5_adapter_middleware_1.injectCustomElementsEs5Adapter(options.compile));
|
262 | app.use('*', compile_middleware_1.babelCompile(options.compile, options.moduleResolution, root, options.packageName, options.componentUrl, options.componentDir));
|
263 | if (options.allowOrigin) {
|
264 | app.use(cors({ origin: options.allowOrigin }));
|
265 | }
|
266 | app.use(`/${componentUrl}/`, polyserve);
|
267 |
|
268 |
|
269 | const entrypoint = options.entrypoint ?
|
270 | path_transformers_1.urlFromPath(root, options.entrypoint) :
|
271 | 'index.html';
|
272 | app.get('/*', (req, res) => {
|
273 | push_1.pushResources(options, req, res);
|
274 | const filePath = req.path;
|
275 | send(req, filePath, { root: root, index: entrypoint, etag: false, lastModified: false })
|
276 | .on('error', (error) => {
|
277 | if (error.status === 404 && !filePathRegex.test(filePath)) {
|
278 |
|
279 |
|
280 | send(req, entrypoint, { root: root }).pipe(res);
|
281 | }
|
282 | else {
|
283 | res.status(error.status || 500);
|
284 | res.type('html');
|
285 | res.end(escapeHtml(error.message));
|
286 | }
|
287 | })
|
288 | .pipe(res);
|
289 | });
|
290 | return app;
|
291 | }
|
292 | exports.getApp = getApp;
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 | function isHttps(protocol) {
|
299 | return ['https/1.1', 'https', 'h2'].indexOf(protocol) > -1;
|
300 | }
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | function getServerUrls(options, server) {
|
308 | options = applyDefaultServerOptions(options);
|
309 | const address = assertNotString(server.address());
|
310 | const serverUrl = {
|
311 | protocol: isHttps(options.protocol) ? 'https' : 'http',
|
312 | hostname: address.address,
|
313 | port: String(address.port),
|
314 | };
|
315 | const componentUrl = Object.assign({}, serverUrl);
|
316 | componentUrl.pathname = `${options.componentUrl}/${options.packageName}/`;
|
317 | return { serverUrl, componentUrl };
|
318 | }
|
319 | exports.getServerUrls = getServerUrls;
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | function assertNodeVersion(options) {
|
325 | if (options.protocol === 'h2') {
|
326 | const matches = /(\d+)\./.exec(process.version);
|
327 | if (matches) {
|
328 | const major = Number(matches[1]);
|
329 | assert(major >= 5, 'h2 requires ALPN which is only supported in node.js >= 5.0');
|
330 | }
|
331 | }
|
332 | }
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 | function createServer(app, options) {
|
340 | return __awaiter(this, void 0, void 0, function* () {
|
341 |
|
342 | const opt = { spdy: { protocols: [options.protocol] } };
|
343 | if (isHttps(options.protocol)) {
|
344 | const keys = yield tls_1.getTLSCertificate(options.keyPath, options.certPath);
|
345 | opt.key = keys.key;
|
346 | opt.cert = keys.cert;
|
347 | }
|
348 | else {
|
349 | opt.spdy.plain = true;
|
350 | opt.spdy.ssl = false;
|
351 | }
|
352 | return http.createServer(opt, app);
|
353 | });
|
354 | }
|
355 |
|
356 |
|
357 |
|
358 |
|
359 | const SAUCE_PORTS = [
|
360 | 8081, 8000, 8001, 8003, 8031,
|
361 | 2000, 2001, 2020, 2109, 2222, 2310, 3000, 3001, 3030, 3210, 3333,
|
362 | 4000, 4001, 4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5432,
|
363 | 6000, 6001, 6060, 6666, 6543, 7000, 7070, 7774, 7777, 8765, 8777,
|
364 | 8888, 9000, 9001, 9080, 9090, 9876, 9877, 9999, 49221, 55001
|
365 | ];
|
366 |
|
367 |
|
368 |
|
369 | function startWithApp(options, app) {
|
370 | return __awaiter(this, void 0, void 0, function* () {
|
371 | options = applyDefaultServerOptions(options);
|
372 | const ports = options.port ? [options.port] : SAUCE_PORTS;
|
373 | const server = yield startWithFirstAvailablePort(options, app, ports);
|
374 | const urls = getServerUrls(options, server);
|
375 | open_browser_1.openBrowser(options, urls.serverUrl, urls.componentUrl);
|
376 | return server;
|
377 | });
|
378 | }
|
379 | exports.startWithApp = startWithApp;
|
380 | function startWithFirstAvailablePort(options, app, ports) {
|
381 | return __awaiter(this, void 0, void 0, function* () {
|
382 | for (const port of ports) {
|
383 | const server = yield tryStartWithPort(options, app, port);
|
384 | if (server) {
|
385 | return server;
|
386 | }
|
387 | }
|
388 | throw new Error(`No available ports. Ports tried: ${JSON.stringify(ports)}`);
|
389 | });
|
390 | }
|
391 | function tryStartWithPort(options, app, port) {
|
392 | return __awaiter(this, void 0, void 0, function* () {
|
393 | const server = yield createServer(app, options);
|
394 | return new Promise((resolve, _reject) => {
|
395 | server.listen(port, options.hostname, () => {
|
396 | resolve(server);
|
397 | });
|
398 | server.on('error', (_err) => {
|
399 | resolve(null);
|
400 | });
|
401 | });
|
402 | });
|
403 | }
|
404 |
|
405 |
|
406 |
|
407 |
|
408 |
|
409 | function assertNotString(value) {
|
410 | assert(typeof value !== 'string');
|
411 | return value;
|
412 | }
|
413 | exports.assertNotString = assertNotString;
|
414 |
|
\ | No newline at end of file |