From 426db1130fbbcec6c31ca48d782264800c569d3b Mon Sep 17 00:00:00 2001 From: "Moo Yeol, Lee (Prescott)" Date: Tue, 7 May 2019 03:15:08 +0900 Subject: [PATCH 1/4] convert code to typescript --- .gitignore | 1 + package.json | 15 +- packages/lambda/package.json | 29 +-- packages/lambda/rollup.config.js | 19 +- packages/lambda/src/flags.js | 17 -- packages/lambda/src/flags.ts | 17 ++ packages/lambda/src/index.test.js | 40 --- packages/lambda/src/index.test.ts | 40 +++ packages/lambda/src/{index.js => index.ts} | 79 +++--- packages/lambda/src/launcher.js | 258 ------------------- packages/lambda/src/launcher.ts | 281 +++++++++++++++++++++ packages/lambda/src/launcher2.ts | 0 packages/lambda/src/utils.js | 41 --- packages/lambda/src/utils.ts | 43 ++++ packages/lambda/tsconfig.json | 13 + packages/lambda/tslint.json | 12 + 16 files changed, 473 insertions(+), 432 deletions(-) delete mode 100644 packages/lambda/src/flags.js create mode 100644 packages/lambda/src/flags.ts delete mode 100644 packages/lambda/src/index.test.js create mode 100644 packages/lambda/src/index.test.ts rename packages/lambda/src/{index.js => index.ts} (65%) delete mode 100644 packages/lambda/src/launcher.js create mode 100644 packages/lambda/src/launcher.ts create mode 100644 packages/lambda/src/launcher2.ts delete mode 100644 packages/lambda/src/utils.js create mode 100644 packages/lambda/src/utils.ts create mode 100644 packages/lambda/tsconfig.json create mode 100644 packages/lambda/tslint.json diff --git a/.gitignore b/.gitignore index 2aaee853..f98092c3 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ event.json headless-chromium-amazonlinux-2017-03.zip .eslintcache packages/lambda/integration-test/headless-chromium +.rpt2_cache diff --git a/package.json b/package.json index aaa5958c..b88e39fe 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "homepage": "https://github.com/adieuadieu/serverless-chrome", "dependencies": {}, "devDependencies": { + "@types/node": "^8.10.48", "ava": "0.25.0", "babel-core": "6.26.3", "babel-eslint": "8.2.3", @@ -66,11 +67,19 @@ "prettier": "1.12.1", "prettier-eslint": "8.8.1", "prettier-eslint-cli": "4.7.1", - "tap-xunit": "2.3.0" + "tap-xunit": "2.3.0", + "ts-node": "^8.1.0" }, "ava": { - "require": "babel-register", - "babel": "inherit" + "require": [ + "ts-node/register", + "babel-register" + ], + "babel": "inherit", + "extensions": [ + "ts", + "js" + ] }, "babel": { "sourceMaps": "inline", diff --git a/packages/lambda/package.json b/packages/lambda/package.json index 4da46b0d..2da44b0d 100644 --- a/packages/lambda/package.json +++ b/packages/lambda/package.json @@ -25,7 +25,7 @@ "homepage": "https://github.com/adieuadieu/serverless-chrome/tree/master/packages/lambda", "license": "MIT", "engines": { - "node": ">= 6.10" + "node": ">= 8.10" }, "config": { "jsSrc": "src/" @@ -34,6 +34,7 @@ "clean": "rm -Rf dist/ ./**.zip", "test": "npm run test:integration", "test:integration": "scripts/test-integration.sh", + "lint": "tslint src/**/*.ts", "build": "rollup -c", "dev": "rollup -c -w", "prepublishOnly": "npm run clean && npm run build", @@ -46,27 +47,13 @@ "extract-zip": "1.6.6" }, "devDependencies": { + "@types/node": "^8.10.48", "ava": "0.25.0", - "babel-core": "6.26.3", - "babel-preset-env": "1.7.0", - "babel-register": "6.26.0", "chrome-launcher": "0.10.2", - "rollup": "0.59.1", - "rollup-plugin-babel": "3.0.4", - "rollup-plugin-node-resolve": "3.3.0" - }, - "babel": { - "sourceMaps": true, - "presets": [ - [ - "env", - { - "modules": "commonjs", - "targets": { - "node": "6.10" - } - } - ] - ] + "rollup": "^1.11.3", + "rollup-plugin-node-resolve": "^4.2.3", + "rollup-plugin-typescript2": "^0.21.0", + "tslint": "^5.16.0", + "typescript": "^3.4.5" } } diff --git a/packages/lambda/rollup.config.js b/packages/lambda/rollup.config.js index 6fa976be..845c78b1 100644 --- a/packages/lambda/rollup.config.js +++ b/packages/lambda/rollup.config.js @@ -1,8 +1,8 @@ -import babel from 'rollup-plugin-babel' import resolve from 'rollup-plugin-node-resolve' +import typescript from 'rollup-plugin-typescript2' export default { - input: 'src/index.js', + input: 'src/index.ts', output: [ { file: 'dist/bundle.cjs.js', format: 'cjs' }, { file: 'dist/bundle.es.js', format: 'es' }, @@ -20,20 +20,7 @@ export default { // ES2015 modules // modulesOnly: true, // Default: false }), - babel({ - babelrc: false, - presets: [ - [ - 'env', - { - modules: false, - targets: { - node: '6.10', - }, - }, - ], - ], - }), + typescript(), ], external: ['fs', 'child_process', 'net', 'http', 'path', 'chrome-launcher'], } diff --git a/packages/lambda/src/flags.js b/packages/lambda/src/flags.js deleted file mode 100644 index 33c8e2e7..00000000 --- a/packages/lambda/src/flags.js +++ /dev/null @@ -1,17 +0,0 @@ -const LOGGING_FLAGS = process.env.DEBUG - ? ['--enable-logging', '--log-level=0', '--v=99'] - : [] - -export default [ - ...LOGGING_FLAGS, - '--disable-dev-shm-usage', // disable /dev/shm tmpfs usage on Lambda - - // @TODO: review if these are still relevant: - '--disable-gpu', - '--single-process', // Currently wont work without this :-( - - // https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/Y95wJUh2AAAJ - '--no-zygote', // helps avoid zombies - - '--no-sandbox', -] diff --git a/packages/lambda/src/flags.ts b/packages/lambda/src/flags.ts new file mode 100644 index 00000000..0f7c358f --- /dev/null +++ b/packages/lambda/src/flags.ts @@ -0,0 +1,17 @@ +const LOGGING_FLAGS = process.env.DEBUG + ? ["--enable-logging", "--log-level=0", "--v=99"] + : []; + +export default [ + ...LOGGING_FLAGS, + "--disable-dev-shm-usage", // disable /dev/shm tmpfs usage on Lambda + + // @TODO: review if these are still relevant: + "--disable-gpu", + "--single-process", // Currently wont work without this :-( + + // https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/Y95wJUh2AAAJ + "--no-zygote", // helps avoid zombies + + "--no-sandbox", +]; diff --git a/packages/lambda/src/index.test.js b/packages/lambda/src/index.test.js deleted file mode 100644 index 7a415ce6..00000000 --- a/packages/lambda/src/index.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import test from 'ava' -import * as chromeFinder from 'chrome-launcher/dist/chrome-finder' -import launch from './index' - -const DEFAULT_TEST_FLAGS = ['--headless'] - -async function getLocalChromePath () { - const installations = await chromeFinder[process.platform]() - - if (installations.length === 0) { - throw new Error('No Chrome Installations Found') - } - - return installations[0] -} - -test.serial('Chrome should launch using LocalChromeLauncher', async (t) => { - const chromePath = await getLocalChromePath() - const chrome = launch({ - flags: DEFAULT_TEST_FLAGS, - chromePath, - port: 9220, - }) - - t.notThrows(chrome) - - const instance = await chrome - - t.truthy(instance.pid, 'pid should be set') - t.truthy(instance.port, 'port should be set') - t.is(instance.port, 9220, 'port should be 9220') - - instance.kill() -}) - -// Covered by the integration-test. -test('Chrome should launch using LambdaChromeLauncher', (t) => { - // @TODO: proper test.. - t.pass() -}) diff --git a/packages/lambda/src/index.test.ts b/packages/lambda/src/index.test.ts new file mode 100644 index 00000000..2f0b7b1a --- /dev/null +++ b/packages/lambda/src/index.test.ts @@ -0,0 +1,40 @@ +import test from "ava"; +import * as chromeFinder from "chrome-launcher/dist/chrome-finder"; +import launch from "./index"; + +const DEFAULT_TEST_FLAGS = ["--headless"]; + +async function getLocalChromePath() { + const installations = await chromeFinder[process.platform](); + + if (installations.length === 0) { + throw new Error("No Chrome Installations Found"); + } + + return installations[0]; +} + +test.serial("Chrome should launch using LocalChromeLauncher", async (t) => { + const chromePath = await getLocalChromePath(); + const chrome = launch({ + flags: DEFAULT_TEST_FLAGS, + chromePath, + port: 9220, + }); + + t.notThrows(chrome); + + const instance = await chrome; + + t.truthy(instance.pid, "pid should be set"); + t.truthy(instance.port, "port should be set"); + t.is(instance.port, 9220, "port should be 9220"); + + instance.kill(); +}); + +// Covered by the integration-test. +test("Chrome should launch using LambdaChromeLauncher", (t) => { + // @TODO: proper test.. + t.pass(); +}); diff --git a/packages/lambda/src/index.js b/packages/lambda/src/index.ts similarity index 65% rename from packages/lambda/src/index.js rename to packages/lambda/src/index.ts index abf4e728..d78be740 100644 --- a/packages/lambda/src/index.js +++ b/packages/lambda/src/index.ts @@ -1,11 +1,11 @@ -import fs from 'fs' +import * as fs from "fs"; +import DEFAULT_CHROME_FLAGS from "./flags"; // import path from 'path' -import LambdaChromeLauncher from './launcher' -import { debug, processExists } from './utils' -import DEFAULT_CHROME_FLAGS from './flags' +import LambdaChromeLauncher from "./launcher"; +import { debug, processExists } from "./utils"; -const DEVTOOLS_PORT = 9222 -const DEVTOOLS_HOST = 'http://127.0.0.1' +const DEVTOOLS_PORT = 9222; +const DEVTOOLS_HOST = "http://127.0.0.1"; // Prepend NSS related libraries and binaries to the library path and path respectively on lambda. /* if (process.env.AWS_EXECUTION_ENV) { @@ -18,68 +18,75 @@ const DEVTOOLS_HOST = 'http://127.0.0.1' // persist the instance across invocations // when the *lambda* container is reused. -let chromeInstance +let chromeInstance: any; -export default async function launch ({ +export interface LaunchOption { + flags?: string[]; + chromePath?: string; + port?: number; + forceLambdaLauncher?: boolean; +} + +export default async function launch({ flags = [], chromePath, port = DEVTOOLS_PORT, forceLambdaLauncher = false, -} = {}) { - const chromeFlags = [...DEFAULT_CHROME_FLAGS, ...flags] +}: LaunchOption = {}) { + const chromeFlags = [...DEFAULT_CHROME_FLAGS, ...flags]; - if (!chromeInstance || !processExists(chromeInstance.pid)) { + if (!chromeInstance || !processExists(chromeInstance.pid!)) { if (process.env.AWS_EXECUTION_ENV || forceLambdaLauncher) { chromeInstance = new LambdaChromeLauncher({ chromePath, chromeFlags, port, - }) + }); } else { // This let's us use chrome-launcher in local development, // but omit it from the lambda function's zip artefact try { // eslint-disable-next-line - const { Launcher: LocalChromeLauncher } = require('chrome-launcher') + const { Launcher: LocalChromeLauncher } = require("chrome-launcher"); chromeInstance = new LocalChromeLauncher({ chromePath, chromeFlags: flags, port, - }) + }); } catch (error) { throw new Error('@serverless-chrome/lambda: Unable to find "chrome-launcher". ' + - "Make sure it's installed if you wish to develop locally.") + "Make sure it's installed if you wish to develop locally."); } } } - debug('Spawning headless shell') + debug("Spawning headless shell"); - const launchStartTime = Date.now() + const launchStartTime = Date.now(); try { - await chromeInstance.launch() + await chromeInstance.launch(); } catch (error) { - debug('Error trying to spawn chrome:', error) + debug("Error trying to spawn chrome:", error); if (process.env.DEBUG) { debug( - 'stdout log:', - fs.readFileSync(`${chromeInstance.userDataDir}/chrome-out.log`, 'utf8') - ) + "stdout log:", + fs.readFileSync(`${chromeInstance.userDataDir}/chrome-out.log`, "utf8"), + ); debug( - 'stderr log:', - fs.readFileSync(`${chromeInstance.userDataDir}/chrome-err.log`, 'utf8') - ) + "stderr log:", + fs.readFileSync(`${chromeInstance.userDataDir}/chrome-err.log`, "utf8"), + ); } - throw new Error('Unable to start Chrome. If you have the DEBUG env variable set,' + - 'there will be more in the logs.') + throw new Error("Unable to start Chrome. If you have the DEBUG env variable set," + + "there will be more in the logs."); } - const launchTime = Date.now() - launchStartTime + const launchTime = Date.now() - launchStartTime; - debug(`It took ${launchTime}ms to spawn chrome.`) + debug(`It took ${launchTime}ms to spawn chrome.`); // unref the chrome instance, otherwise the lambda process won't end correctly /* @TODO: make this an option? @@ -89,8 +96,8 @@ export default async function launch ({ without unreffing chrome. */ if (chromeInstance.chrome) { - chromeInstance.chrome.removeAllListeners() - chromeInstance.chrome.unref() + chromeInstance.chrome.removeAllListeners(); + chromeInstance.chrome.unref(); } return { @@ -104,14 +111,14 @@ export default async function launch ({ launchTime, didLaunch: !!chromeInstance.pid, }, - async kill () { + async kill() { // Defer killing chrome process to the end of the execution stack // so that the node process doesn't end before chrome exists, // avoiding chrome becoming orphaned. setTimeout(async () => { - chromeInstance.kill() - chromeInstance = undefined - }, 0) + chromeInstance.kill(); + chromeInstance = undefined; + }, 0); }, - } + }; } diff --git a/packages/lambda/src/launcher.js b/packages/lambda/src/launcher.js deleted file mode 100644 index 8db546e5..00000000 --- a/packages/lambda/src/launcher.js +++ /dev/null @@ -1,258 +0,0 @@ -/* - Large portion of this is inspired by/taken from Lighthouse/chrome-launcher. - It is Copyright Google Inc, licensed under Apache License, Version 2.0. - https://github.com/GoogleChrome/lighthouse/blob/master/chrome-launcher/chrome-launcher.ts - - We ship a modified version because the original verion comes with too - many dependencies which complicates packaging of serverless services. -*/ - -import path from 'path' -import fs from 'fs' -import { execSync, spawn } from 'child_process' -import net from 'net' -import http from 'http' -import { delay, debug, makeTempDir, clearConnection } from './utils' -import DEFAULT_CHROME_FLAGS from './flags' - -const CHROME_PATH = path.resolve(__dirname, './headless-chromium') - -export default class Launcher { - constructor (options = {}) { - const { - chromePath = CHROME_PATH, - chromeFlags = [], - startingUrl = 'about:blank', - port = 0, - } = options - - this.tmpDirandPidFileReady = false - this.pollInterval = 500 - this.pidFile = '' - this.startingUrl = 'about:blank' - this.outFile = null - this.errFile = null - this.chromePath = CHROME_PATH - this.chromeFlags = [] - this.requestedPort = 0 - this.userDataDir = '' - this.port = 9222 - this.pid = null - this.chrome = undefined - - this.options = options - this.startingUrl = startingUrl - this.chromeFlags = chromeFlags - this.chromePath = chromePath - this.requestedPort = port - } - - get flags () { - return [ - ...DEFAULT_CHROME_FLAGS, - `--remote-debugging-port=${this.port}`, - `--user-data-dir=${this.userDataDir}`, - '--disable-setuid-sandbox', - ...this.chromeFlags, - this.startingUrl, - ] - } - - prepare () { - this.userDataDir = this.options.userDataDir || makeTempDir() - this.outFile = fs.openSync(`${this.userDataDir}/chrome-out.log`, 'a') - this.errFile = fs.openSync(`${this.userDataDir}/chrome-err.log`, 'a') - this.pidFile = '/tmp/chrome.pid' - this.tmpDirandPidFileReady = true - } - - // resolves if ready, rejects otherwise - isReady () { - return new Promise((resolve, reject) => { - const client = net.createConnection(this.port) - - client.once('error', (error) => { - clearConnection(client) - reject(error) - }) - - client.once('connect', () => { - clearConnection(client) - resolve() - }) - }) - } - - // resolves when debugger is ready, rejects after 10 polls - waitUntilReady () { - const launcher = this - - return new Promise((resolve, reject) => { - let retries = 0; - (function poll () { - debug('Waiting for Chrome', retries) - - launcher - .isReady() - .then(() => { - debug('Started Chrome') - resolve() - }) - .catch((error) => { - retries += 1 - - if (retries > 10) { - return reject(error) - } - - return delay(launcher.pollInterval).then(poll) - }) - }()) - }) - } - - // resolves when chrome is killed, rejects after 10 polls - waitUntilKilled () { - return Promise.all([ - new Promise((resolve, reject) => { - let retries = 0 - const server = http.createServer() - - server.once('listening', () => { - debug('Confirmed Chrome killed') - server.close(resolve) - }) - - server.on('error', () => { - retries += 1 - - debug('Waiting for Chrome to terminate..', retries) - - if (retries > 10) { - reject(new Error('Chrome is still running after 10 retries')) - } - - setTimeout(() => { - server.listen(this.port) - }, this.pollInterval) - }) - - server.listen(this.port) - }), - new Promise((resolve) => { - this.chrome.on('close', resolve) - }), - ]) - } - - async spawn () { - const spawnPromise = new Promise(async (resolve) => { - if (this.chrome) { - debug(`Chrome already running with pid ${this.chrome.pid}.`) - return resolve(this.chrome.pid) - } - - const chrome = spawn(this.chromePath, this.flags, { - detached: true, - stdio: ['ignore', this.outFile, this.errFile], - }) - - this.chrome = chrome - - // unref the chrome instance, otherwise the lambda process won't end correctly - if (chrome.chrome) { - chrome.chrome.removeAllListeners() - chrome.chrome.unref() - } - - fs.writeFileSync(this.pidFile, chrome.pid.toString()) - - debug( - 'Launcher', - `Chrome running with pid ${chrome.pid} on port ${this.port}.` - ) - - return resolve(chrome.pid) - }) - - const pid = await spawnPromise - await this.waitUntilReady() - return pid - } - - async launch () { - if (this.requestedPort !== 0) { - this.port = this.requestedPort - - // If an explict port is passed first look for an open connection... - try { - return await this.isReady() - } catch (err) { - debug( - 'ChromeLauncher', - `No debugging port found on port ${ - this.port - }, launching a new Chrome.` - ) - } - } - - if (!this.tmpDirandPidFileReady) { - this.prepare() - } - - this.pid = await this.spawn() - return Promise.resolve() - } - - kill () { - return new Promise(async (resolve, reject) => { - if (this.chrome) { - debug('Trying to terminate Chrome instance') - - try { - process.kill(-this.chrome.pid) - - debug('Waiting for Chrome to terminate..') - await this.waitUntilKilled() - debug('Chrome successfully terminated.') - - this.destroyTemp() - - delete this.chrome - return resolve() - } catch (error) { - debug('Chrome could not be killed', error) - return reject(error) - } - } else { - // fail silently as we did not start chrome - return resolve() - } - }) - } - - destroyTemp () { - return new Promise((resolve) => { - // Only clean up the tmp dir if we created it. - if ( - this.userDataDir === undefined || - this.options.userDataDir !== undefined - ) { - return resolve() - } - - if (this.outFile) { - fs.closeSync(this.outFile) - delete this.outFile - } - - if (this.errFile) { - fs.closeSync(this.errFile) - delete this.errFile - } - - return execSync(`rm -Rf ${this.userDataDir}`, resolve) - }) - } -} diff --git a/packages/lambda/src/launcher.ts b/packages/lambda/src/launcher.ts new file mode 100644 index 00000000..b5b9f687 --- /dev/null +++ b/packages/lambda/src/launcher.ts @@ -0,0 +1,281 @@ +/* + Large portion of this is inspired by/taken from Lighthouse/chrome-launcher. + It is Copyright Google Inc, licensed under Apache License, Version 2.0. + https://github.com/GoogleChrome/lighthouse/blob/master/chrome-launcher/chrome-launcher.ts + + We ship a modified version because the original verion comes with too + many dependencies which complicates packaging of serverless services. +*/ + +import { execSync, spawn } from "child_process"; +import * as fs from "fs"; +import * as http from "http"; +import * as net from "net"; +import * as path from "path"; +import DEFAULT_CHROME_FLAGS from "./flags"; +import { clearConnection, debug, delay, makeTempDir } from "./utils"; + +const CHROME_PATH = path.resolve(__dirname, "./headless-chromium"); + +export interface LauncherOptions { + chromePath?: string; + chromeFlags?: string[]; + startingUrl?: string; + userDataDir?: string; + port?: number; +} + +export default class Launcher { + public tmpDirandPidFileReady: boolean; + public pollInterval: number; + public pidFile: string; + public startingUrl: string; + public outFile: number | null; + public errFile: number | null; + public chromePath: string; + public chromeFlags: string[]; + public requestedPort: number; + public userDataDir: string; + public port: number; + public pid: number | null; + public chrome: any; + public options: LauncherOptions; + + constructor(options: LauncherOptions = {}) { + const { + chromePath = CHROME_PATH, + chromeFlags = [], + startingUrl = "about:blank", + port = 0, + } = options; + + this.tmpDirandPidFileReady = false; + this.pollInterval = 500; + this.pidFile = ""; + this.startingUrl = "about:blank"; + this.outFile = null; + this.errFile = null; + this.chromePath = CHROME_PATH; + this.chromeFlags = []; + this.requestedPort = 0; + this.userDataDir = ""; + this.port = 9222; + this.pid = null; + this.chrome = undefined; + + this.options = options; + this.startingUrl = startingUrl; + this.chromeFlags = chromeFlags; + this.chromePath = chromePath; + this.requestedPort = port; + } + + get flags() { + return [ + ...DEFAULT_CHROME_FLAGS, + `--remote-debugging-port=${this.port}`, + `--user-data-dir=${this.userDataDir}`, + "--disable-setuid-sandbox", + ...this.chromeFlags, + this.startingUrl, + ]; + } + + public prepare() { + this.userDataDir = this.options.userDataDir || makeTempDir(); + this.outFile = fs.openSync(`${this.userDataDir}/chrome-out.log`, "a"); + this.errFile = fs.openSync(`${this.userDataDir}/chrome-err.log`, "a"); + this.pidFile = "/tmp/chrome.pid"; + this.tmpDirandPidFileReady = true; + } + + // resolves if ready, rejects otherwise + public isReady() { + return new Promise((resolve, reject) => { + const client = net.createConnection(this.port); + + client.once("error", (error) => { + clearConnection(client); + reject(error); + }); + + client.once("connect", () => { + clearConnection(client); + resolve(); + }); + }); + } + + // resolves when debugger is ready, rejects after 10 polls + public waitUntilReady() { + const launcher = this; + + return new Promise((resolve, reject) => { + let retries = 0; + (function poll() { + debug("Waiting for Chrome", retries); + + launcher + .isReady() + .then(() => { + debug("Started Chrome"); + resolve(); + }) + .catch((error) => { + retries += 1; + + if (retries > 10) { + return reject(error); + } + + return delay(launcher.pollInterval).then(poll); + }); + }()); + }); + } + + // resolves when chrome is killed, rejects after 10 polls + public waitUntilKilled() { + return Promise.all([ + new Promise((resolve, reject) => { + let retries = 0; + const server = http.createServer(); + + server.once("listening", () => { + debug("Confirmed Chrome killed"); + server.close(resolve); + }); + + server.on("error", () => { + retries += 1; + + debug("Waiting for Chrome to terminate..", retries); + + if (retries > 10) { + reject(new Error("Chrome is still running after 10 retries")); + } + + setTimeout(() => { + server.listen(this.port); + }, this.pollInterval); + }); + + server.listen(this.port); + }), + new Promise((resolve) => { + this.chrome.on("close", resolve); + }), + ]); + } + + public async spawn() { + const spawnPromise = new Promise(async (resolve) => { + if (this.chrome) { + debug(`Chrome already running with pid ${this.chrome.pid}.`); + return resolve(this.chrome.pid); + } + + const chrome = spawn(this.chromePath, this.flags, { + detached: true, + stdio: ["ignore", this.outFile, this.errFile], + }); + + this.chrome = chrome; + + // unref the chrome instance, otherwise the lambda process won't end correctly + if ((chrome as any).chrome) { + (chrome as any).chrome.removeAllListeners(); + (chrome as any).chrome.unref(); + } + + fs.writeFileSync(this.pidFile, chrome.pid.toString()); + + debug( + "Launcher", + `Chrome running with pid ${chrome.pid} on port ${this.port}.`, + ); + + return resolve(chrome.pid); + }); + + const pid = await spawnPromise; + await this.waitUntilReady(); + return pid; + } + + public async launch() { + if (this.requestedPort !== 0) { + this.port = this.requestedPort; + + // If an explict port is passed first look for an open connection... + try { + return await this.isReady(); + } catch (err) { + debug( + "ChromeLauncher", + `No debugging port found on port ${ + this.port + }, launching a new Chrome.`, + ); + } + } + + if (!this.tmpDirandPidFileReady) { + this.prepare(); + } + + this.pid = await this.spawn(); + return Promise.resolve(); + } + + public kill() { + return new Promise(async (resolve, reject) => { + if (this.chrome) { + debug("Trying to terminate Chrome instance"); + + try { + process.kill(-this.chrome.pid); + + debug("Waiting for Chrome to terminate.."); + await this.waitUntilKilled(); + debug("Chrome successfully terminated."); + + this.destroyTemp(); + + delete this.chrome; + return resolve(); + } catch (error) { + debug("Chrome could not be killed", error); + return reject(error); + } + } else { + // fail silently as we did not start chrome + return resolve(); + } + }); + } + + public destroyTemp() { + return new Promise((resolve) => { + // Only clean up the tmp dir if we created it. + if ( + this.userDataDir === undefined || + this.options.userDataDir !== undefined + ) { + return resolve(); + } + + if (this.outFile) { + fs.closeSync(this.outFile); + delete this.outFile; + } + + if (this.errFile) { + fs.closeSync(this.errFile); + delete this.errFile; + } + + return (execSync as any)(`rm -Rf ${this.userDataDir}`, resolve); + }); + } +} diff --git a/packages/lambda/src/launcher2.ts b/packages/lambda/src/launcher2.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/lambda/src/utils.js b/packages/lambda/src/utils.js deleted file mode 100644 index 8631c955..00000000 --- a/packages/lambda/src/utils.js +++ /dev/null @@ -1,41 +0,0 @@ -import { execSync } from 'child_process' - -export function clearConnection (client) { - if (client) { - client.removeAllListeners() - client.end() - client.destroy() - client.unref() - } -} - -export function debug (...args) { - return process.env.DEBUG - ? console.log('@serverless-chrome/lambda:', ...args) - : undefined -} - -export async function delay (time) { - return new Promise(resolve => setTimeout(resolve, time)) -} - -export function makeTempDir () { - return execSync('mktemp -d -t chrome.XXXXXXX') - .toString() - .trim() -} - -/** - * Checks if a process currently exists by process id. - * @param pid number process id to check if exists - * @returns boolean true if process exists, false if otherwise - */ -export function processExists (pid) { - let exists = true - try { - process.kill(pid, 0) - } catch (error) { - exists = false - } - return exists -} diff --git a/packages/lambda/src/utils.ts b/packages/lambda/src/utils.ts new file mode 100644 index 00000000..6166f001 --- /dev/null +++ b/packages/lambda/src/utils.ts @@ -0,0 +1,43 @@ +import { execSync } from "child_process"; +import { Socket } from "net"; + +export function clearConnection(client: Socket) { + if (client) { + client.removeAllListeners(); + client.end(); + client.destroy(); + client.unref(); + } +} + +export function debug(...args: any[]): void { + return process.env.DEBUG + // tslint:disable-next-line + ? console.log("@serverless-chrome/lambda:", ...args) + : undefined; +} + +export async function delay(time: number): Promise { + return new Promise((resolve) => setTimeout(resolve, time)); +} + +export function makeTempDir(): string { + return execSync("mktemp -d -t chrome.XXXXXXX") + .toString() + .trim(); +} + +/** + * Checks if a process currently exists by process id. + * @param pid number process id to check if exists + * @returns boolean true if process exists, false if otherwise + */ +export function processExists(pid: number): boolean { + let exists = true; + try { + process.kill(pid, 0); + } catch (error) { + exists = false; + } + return exists; +} diff --git a/packages/lambda/tsconfig.json b/packages/lambda/tsconfig.json new file mode 100644 index 00000000..51121b69 --- /dev/null +++ b/packages/lambda/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "es2015", + "target": "es2017", + "noImplicitAny": true, + "declaration": true, + "sourceMap": true, + "strictNullChecks": true + }, + "include": [ + "src/*.ts" + ] +} diff --git a/packages/lambda/tslint.json b/packages/lambda/tslint.json new file mode 100644 index 00000000..5f27e42f --- /dev/null +++ b/packages/lambda/tslint.json @@ -0,0 +1,12 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "object-literal-sort-keys": false, + "interface-name": [true, "never-prefix"] + }, + "rulesDirectory": [] +} From a29d93ebe2adbe00289cf3b8818241c9c55aea78 Mon Sep 17 00:00:00 2001 From: "Moo Yeol, Lee (Prescott)" Date: Wed, 8 May 2019 05:07:37 +0900 Subject: [PATCH 2/4] migrate to typescript --- packages/lambda/package.json | 2 + packages/lambda/rollup.config.js | 2 +- packages/lambda/src/flags.ts | 17 -- packages/lambda/src/index.test.ts | 4 +- packages/lambda/src/index.ts | 64 ++---- packages/lambda/src/launcher.ts | 366 +++++++++++++----------------- packages/lambda/src/launcher2.ts | 0 packages/lambda/src/utils.ts | 43 ---- packages/lambda/tsconfig.json | 5 +- tsconfig.json | 13 ++ 10 files changed, 205 insertions(+), 311 deletions(-) delete mode 100644 packages/lambda/src/flags.ts delete mode 100644 packages/lambda/src/launcher2.ts delete mode 100644 packages/lambda/src/utils.ts create mode 100644 tsconfig.json diff --git a/packages/lambda/package.json b/packages/lambda/package.json index 2da44b0d..d9cc3ebf 100644 --- a/packages/lambda/package.json +++ b/packages/lambda/package.json @@ -44,9 +44,11 @@ "upgrade-dependencies": "yarn upgrade-interactive --latest --exact" }, "dependencies": { + "debug": "^4.1.1", "extract-zip": "1.6.6" }, "devDependencies": { + "@types/debug": "^4.1.4", "@types/node": "^8.10.48", "ava": "0.25.0", "chrome-launcher": "0.10.2", diff --git a/packages/lambda/rollup.config.js b/packages/lambda/rollup.config.js index 845c78b1..953107e9 100644 --- a/packages/lambda/rollup.config.js +++ b/packages/lambda/rollup.config.js @@ -22,5 +22,5 @@ export default { }), typescript(), ], - external: ['fs', 'child_process', 'net', 'http', 'path', 'chrome-launcher'], + external: ['fs', 'child_process', 'net', 'path', 'chrome-launcher', 'debug'], } diff --git a/packages/lambda/src/flags.ts b/packages/lambda/src/flags.ts deleted file mode 100644 index 0f7c358f..00000000 --- a/packages/lambda/src/flags.ts +++ /dev/null @@ -1,17 +0,0 @@ -const LOGGING_FLAGS = process.env.DEBUG - ? ["--enable-logging", "--log-level=0", "--v=99"] - : []; - -export default [ - ...LOGGING_FLAGS, - "--disable-dev-shm-usage", // disable /dev/shm tmpfs usage on Lambda - - // @TODO: review if these are still relevant: - "--disable-gpu", - "--single-process", // Currently wont work without this :-( - - // https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/Y95wJUh2AAAJ - "--no-zygote", // helps avoid zombies - - "--no-sandbox", -]; diff --git a/packages/lambda/src/index.test.ts b/packages/lambda/src/index.test.ts index 2f0b7b1a..b1306baa 100644 --- a/packages/lambda/src/index.test.ts +++ b/packages/lambda/src/index.test.ts @@ -16,7 +16,7 @@ async function getLocalChromePath() { test.serial("Chrome should launch using LocalChromeLauncher", async (t) => { const chromePath = await getLocalChromePath(); - const chrome = launch({ + const chrome = await launch({ flags: DEFAULT_TEST_FLAGS, chromePath, port: 9220, @@ -30,7 +30,7 @@ test.serial("Chrome should launch using LocalChromeLauncher", async (t) => { t.truthy(instance.port, "port should be set"); t.is(instance.port, 9220, "port should be 9220"); - instance.kill(); + await instance.kill(); }); // Covered by the integration-test. diff --git a/packages/lambda/src/index.ts b/packages/lambda/src/index.ts index d78be740..f8f8da2e 100644 --- a/packages/lambda/src/index.ts +++ b/packages/lambda/src/index.ts @@ -1,10 +1,8 @@ -import * as fs from "fs"; -import DEFAULT_CHROME_FLAGS from "./flags"; -// import path from 'path' +import debug from "debug"; import LambdaChromeLauncher from "./launcher"; -import { debug, processExists } from "./utils"; -const DEVTOOLS_PORT = 9222; +const log = debug("@serverless-chrome/lambda"); + const DEVTOOLS_HOST = "http://127.0.0.1"; // Prepend NSS related libraries and binaries to the library path and path respectively on lambda. @@ -18,7 +16,7 @@ const DEVTOOLS_HOST = "http://127.0.0.1"; // persist the instance across invocations // when the *lambda* container is reused. -let chromeInstance: any; +let chromeInstance: LambdaChromeLauncher | undefined; export interface LaunchOption { flags?: string[]; @@ -28,13 +26,11 @@ export interface LaunchOption { } export default async function launch({ - flags = [], + flags: chromeFlags = [], chromePath, - port = DEVTOOLS_PORT, + port, forceLambdaLauncher = false, }: LaunchOption = {}) { - const chromeFlags = [...DEFAULT_CHROME_FLAGS, ...flags]; - if (!chromeInstance || !processExists(chromeInstance.pid!)) { if (process.env.AWS_EXECUTION_ENV || forceLambdaLauncher) { chromeInstance = new LambdaChromeLauncher({ @@ -44,15 +40,14 @@ export default async function launch({ }); } else { // This let's us use chrome-launcher in local development, - // but omit it from the lambda function's zip artefact + // but omit it from the lambda function's zip artifact try { - // eslint-disable-next-line const { Launcher: LocalChromeLauncher } = require("chrome-launcher"); - chromeInstance = new LocalChromeLauncher({ + chromeInstance = (new LocalChromeLauncher({ chromePath, - chromeFlags: flags, + chromeFlags, port, - }); + })) as LambdaChromeLauncher; } catch (error) { throw new Error('@serverless-chrome/lambda: Unable to find "chrome-launcher". ' + "Make sure it's installed if you wish to develop locally."); @@ -60,25 +55,14 @@ export default async function launch({ } } - debug("Spawning headless shell"); + log("Spawning headless shell"); const launchStartTime = Date.now(); try { await chromeInstance.launch(); } catch (error) { - debug("Error trying to spawn chrome:", error); - - if (process.env.DEBUG) { - debug( - "stdout log:", - fs.readFileSync(`${chromeInstance.userDataDir}/chrome-out.log`, "utf8"), - ); - debug( - "stderr log:", - fs.readFileSync(`${chromeInstance.userDataDir}/chrome-err.log`, "utf8"), - ); - } + log("Error trying to spawn chrome:", error); throw new Error("Unable to start Chrome. If you have the DEBUG env variable set," + "there will be more in the logs."); @@ -86,7 +70,7 @@ export default async function launch({ const launchTime = Date.now() - launchStartTime; - debug(`It took ${launchTime}ms to spawn chrome.`); + log("It took %dms to spawn chrome.", launchTime); // unref the chrome instance, otherwise the lambda process won't end correctly /* @TODO: make this an option? @@ -96,7 +80,6 @@ export default async function launch({ without unreffing chrome. */ if (chromeInstance.chrome) { - chromeInstance.chrome.removeAllListeners(); chromeInstance.chrome.unref(); } @@ -104,21 +87,24 @@ export default async function launch({ pid: chromeInstance.pid, port: chromeInstance.port, url: `${DEVTOOLS_HOST}:${chromeInstance.port}`, - log: `${chromeInstance.userDataDir}/chrome-out.log`, - errorLog: `${chromeInstance.userDataDir}/chrome-err.log`, - pidFile: `${chromeInstance.userDataDir}/chrome.pid`, metaData: { launchTime, didLaunch: !!chromeInstance.pid, }, async kill() { - // Defer killing chrome process to the end of the execution stack - // so that the node process doesn't end before chrome exists, - // avoiding chrome becoming orphaned. - setTimeout(async () => { - chromeInstance.kill(); + if (chromeInstance) { + await chromeInstance.kill(); chromeInstance = undefined; - }, 0); + } }, }; } + +function processExists(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch (error) { + return false; + } +} diff --git a/packages/lambda/src/launcher.ts b/packages/lambda/src/launcher.ts index b5b9f687..6f2d25fe 100644 --- a/packages/lambda/src/launcher.ts +++ b/packages/lambda/src/launcher.ts @@ -7,275 +7,231 @@ many dependencies which complicates packaging of serverless services. */ -import { execSync, spawn } from "child_process"; -import * as fs from "fs"; -import * as http from "http"; +// Removed chrome process stdout/stderr file logging +// Remove responsibility of user data directory setup/teardown + +import { ChildProcess, spawn } from "child_process"; +import debug from "debug"; import * as net from "net"; import * as path from "path"; -import DEFAULT_CHROME_FLAGS from "./flags"; -import { clearConnection, debug, delay, makeTempDir } from "./utils"; + +const DEFAULT_CHROME_FLAGS = new Set([ + "--disable-dev-shm-usage", // disable /dev/shm tmpfs usage on Lambda + + // @TODO: review if these are still relevant: + "--disable-gpu", + "--single-process", // Currently wont work without this :-( + + // https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/Y95wJUh2AAAJ + "--no-zygote", // helps avoid zombies + + "--no-sandbox", +]); const CHROME_PATH = path.resolve(__dirname, "./headless-chromium"); export interface LauncherOptions { + pollInterval?: number; chromePath?: string; chromeFlags?: string[]; startingUrl?: string; - userDataDir?: string; port?: number; + debug?: boolean; } -export default class Launcher { - public tmpDirandPidFileReady: boolean; - public pollInterval: number; - public pidFile: string; - public startingUrl: string; - public outFile: number | null; - public errFile: number | null; - public chromePath: string; - public chromeFlags: string[]; - public requestedPort: number; - public userDataDir: string; - public port: number; - public pid: number | null; - public chrome: any; - public options: LauncherOptions; +export default class LambdaChromeLauncher { + public chrome?: ChildProcess; + + private readonly requestedPort?: number; + private readonly chromePath: string; + private readonly chromeFlags: Set; + private readonly pollInterval: number; + private readonly startingUrl: string; + private readonly debug: boolean; + + private readonly log = debug("@serverless-chrome/lambda"); constructor(options: LauncherOptions = {}) { const { + pollInterval = 500, chromePath = CHROME_PATH, chromeFlags = [], startingUrl = "about:blank", - port = 0, + port, } = options; - this.tmpDirandPidFileReady = false; - this.pollInterval = 500; - this.pidFile = ""; - this.startingUrl = "about:blank"; - this.outFile = null; - this.errFile = null; - this.chromePath = CHROME_PATH; - this.chromeFlags = []; - this.requestedPort = 0; - this.userDataDir = ""; - this.port = 9222; - this.pid = null; - this.chrome = undefined; - - this.options = options; + this.debug = !!options.debug; + this.pollInterval = pollInterval; + this.requestedPort = port; this.startingUrl = startingUrl; - this.chromeFlags = chromeFlags; this.chromePath = chromePath; - this.requestedPort = port; - } - - get flags() { - return [ + this.chromeFlags = new Set([ + ...this.debug ? ["--enable-logging", "--log-level=0", "--v=99"] : [], ...DEFAULT_CHROME_FLAGS, `--remote-debugging-port=${this.port}`, - `--user-data-dir=${this.userDataDir}`, "--disable-setuid-sandbox", + ...chromeFlags, + ]); + } + + public get port() { + return this.requestedPort || 9222; + } + + public get flags() { + return [ ...this.chromeFlags, this.startingUrl, ]; } - public prepare() { - this.userDataDir = this.options.userDataDir || makeTempDir(); - this.outFile = fs.openSync(`${this.userDataDir}/chrome-out.log`, "a"); - this.errFile = fs.openSync(`${this.userDataDir}/chrome-err.log`, "a"); - this.pidFile = "/tmp/chrome.pid"; - this.tmpDirandPidFileReady = true; + public get pid() { + return this.chrome ? this.chrome.pid : null; } - // resolves if ready, rejects otherwise - public isReady() { - return new Promise((resolve, reject) => { - const client = net.createConnection(this.port); - - client.once("error", (error) => { - clearConnection(client); - reject(error); - }); + public async launch(): Promise { + if (this.requestedPort) { + // If an explicit port is passed first look for an open connection... + try { + return await this.ensureReady(); + } catch (e) { + this.log("No debugging port found on port %d, launching a new Chrome", this.port); + } + } - client.once("connect", () => { - clearConnection(client); - resolve(); - }); - }); + if (this.chrome) { + this.log(`Chrome already running with pid %d.`, this.chrome.pid); + } else { + this.chrome = await this.spawn(); + await this.waitUntilReady(); + } } - // resolves when debugger is ready, rejects after 10 polls - public waitUntilReady() { - const launcher = this; - - return new Promise((resolve, reject) => { - let retries = 0; - (function poll() { - debug("Waiting for Chrome", retries); - - launcher - .isReady() - .then(() => { - debug("Started Chrome"); - resolve(); - }) - .catch((error) => { - retries += 1; - - if (retries > 10) { - return reject(error); - } - - return delay(launcher.pollInterval).then(poll); - }); - }()); - }); + public async kill() { + if (this.chrome) { + this.log("Trying to terminate Chrome instance"); + try { + process.kill(-this.chrome.pid); + + this.log("Waiting for Chrome to terminate.."); + await this.waitUntilKilled(); + this.chrome = undefined; + this.log("Chrome successfully terminated."); + } catch (e) { + this.log("Chrome could not be killed", e); + throw e; + } + } } - // resolves when chrome is killed, rejects after 10 polls - public waitUntilKilled() { - return Promise.all([ - new Promise((resolve, reject) => { - let retries = 0; - const server = http.createServer(); - - server.once("listening", () => { - debug("Confirmed Chrome killed"); - server.close(resolve); - }); - - server.on("error", () => { - retries += 1; - - debug("Waiting for Chrome to terminate..", retries); + private spawn(): ChildProcess { + const proc = spawn(this.chromePath, this.flags, { + detached: true, + stdio: this.debug ? ["ignore", process.stdout, process.stderr] : "ignore", + }); - if (retries > 10) { - reject(new Error("Chrome is still running after 10 retries")); - } + // unref the chrome instance, otherwise the lambda process won't end correctly + proc.unref(); - setTimeout(() => { - server.listen(this.port); - }, this.pollInterval); - }); + this.log("Chrome running with pid %d on port %d.", proc.pid, this.port); - server.listen(this.port); - }), - new Promise((resolve) => { - this.chrome.on("close", resolve); - }), - ]); + return proc; } - public async spawn() { - const spawnPromise = new Promise(async (resolve) => { - if (this.chrome) { - debug(`Chrome already running with pid ${this.chrome.pid}.`); - return resolve(this.chrome.pid); + // resolves if ready, rejects otherwise + private ensureReady(): Promise { + return new Promise((resolve, reject) => { + const client = net.createConnection(this.port) + .setTimeout(1000) // @todo make it customizable? + .once("error", onError) + .once("connect", onConnect) + .once("timeout", onTimeout); + + function onError(e: Error) { + client + .removeListener("connect", onConnect) + .removeListener("timeout", onTimeout); + + reject(e); } - const chrome = spawn(this.chromePath, this.flags, { - detached: true, - stdio: ["ignore", this.outFile, this.errFile], - }); + function onConnect() { + client + .removeListener("error", onError) + .removeListener("timeout", onTimeout); - this.chrome = chrome; - - // unref the chrome instance, otherwise the lambda process won't end correctly - if ((chrome as any).chrome) { - (chrome as any).chrome.removeAllListeners(); - (chrome as any).chrome.unref(); + client.end(); + resolve(); } - fs.writeFileSync(this.pidFile, chrome.pid.toString()); + function onTimeout() { + client + .removeListener("error", onError) + .removeListener("connect", onConnect); - debug( - "Launcher", - `Chrome running with pid ${chrome.pid} on port ${this.port}.`, - ); - - return resolve(chrome.pid); + reject(new Error("Connection timed out")); + } }); - - const pid = await spawnPromise; - await this.waitUntilReady(); - return pid; } - public async launch() { - if (this.requestedPort !== 0) { - this.port = this.requestedPort; + // resolves when debugger is ready, rejects after 10 polls + private async waitUntilReady(): Promise { + const MAX_RETRIES = 10; + let retries = 0; + + while (retries++ < MAX_RETRIES) { + log("Waiting for Chrome", retries); - // If an explict port is passed first look for an open connection... try { - return await this.isReady(); - } catch (err) { - debug( - "ChromeLauncher", - `No debugging port found on port ${ - this.port - }, launching a new Chrome.`, - ); + await this.ensureReady(); + this.log("Started Chrome"); + return; + } catch (e) { + await this.sleep(this.pollInterval); } } - - if (!this.tmpDirandPidFileReady) { - this.prepare(); - } - - this.pid = await this.spawn(); - return Promise.resolve(); } - public kill() { - return new Promise(async (resolve, reject) => { - if (this.chrome) { - debug("Trying to terminate Chrome instance"); + // resolves when chrome is killed, rejects after 10 polls + private async waitUntilKilled(): Promise { + await Promise.all([ + new Promise((resolve, reject) => { + const self = this; + const MAX_RETRIES = 10; + let retries = 0; + + const server = net.createServer() + .on("listening", onListen) + .on("error", onError); - try { - process.kill(-this.chrome.pid); + function onListen() { + self.log("Confirmed Chrome killed"); + server.close(resolve); + } - debug("Waiting for Chrome to terminate.."); - await this.waitUntilKilled(); - debug("Chrome successfully terminated."); + function onError(e: Error) { + if (retries++ < MAX_RETRIES) { + setTimeout(() => server.listen(self.port), self.pollInterval); + } else { + reject(new Error("Chrome is still running after 10 retries")); + } + } - this.destroyTemp(); + this.log("Waiting for Chrome to terminate..", retries); - delete this.chrome; + server.listen(this.port); + }), + new Promise((resolve) => { + if (!this.chrome || this.chrome.killed) { return resolve(); - } catch (error) { - debug("Chrome could not be killed", error); - return reject(error); + } else { + this.chrome.once("close", resolve); } - } else { - // fail silently as we did not start chrome - return resolve(); - } - }); + }), + ]); } - public destroyTemp() { - return new Promise((resolve) => { - // Only clean up the tmp dir if we created it. - if ( - this.userDataDir === undefined || - this.options.userDataDir !== undefined - ) { - return resolve(); - } - - if (this.outFile) { - fs.closeSync(this.outFile); - delete this.outFile; - } - - if (this.errFile) { - fs.closeSync(this.errFile); - delete this.errFile; - } - - return (execSync as any)(`rm -Rf ${this.userDataDir}`, resolve); - }); + private sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); } } diff --git a/packages/lambda/src/launcher2.ts b/packages/lambda/src/launcher2.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/lambda/src/utils.ts b/packages/lambda/src/utils.ts deleted file mode 100644 index 6166f001..00000000 --- a/packages/lambda/src/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { execSync } from "child_process"; -import { Socket } from "net"; - -export function clearConnection(client: Socket) { - if (client) { - client.removeAllListeners(); - client.end(); - client.destroy(); - client.unref(); - } -} - -export function debug(...args: any[]): void { - return process.env.DEBUG - // tslint:disable-next-line - ? console.log("@serverless-chrome/lambda:", ...args) - : undefined; -} - -export async function delay(time: number): Promise { - return new Promise((resolve) => setTimeout(resolve, time)); -} - -export function makeTempDir(): string { - return execSync("mktemp -d -t chrome.XXXXXXX") - .toString() - .trim(); -} - -/** - * Checks if a process currently exists by process id. - * @param pid number process id to check if exists - * @returns boolean true if process exists, false if otherwise - */ -export function processExists(pid: number): boolean { - let exists = true; - try { - process.kill(pid, 0); - } catch (error) { - exists = false; - } - return exists; -} diff --git a/packages/lambda/tsconfig.json b/packages/lambda/tsconfig.json index 51121b69..49b81747 100644 --- a/packages/lambda/tsconfig.json +++ b/packages/lambda/tsconfig.json @@ -6,8 +6,5 @@ "declaration": true, "sourceMap": true, "strictNullChecks": true - }, - "include": [ - "src/*.ts" - ] + } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..51121b69 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "es2015", + "target": "es2017", + "noImplicitAny": true, + "declaration": true, + "sourceMap": true, + "strictNullChecks": true + }, + "include": [ + "src/*.ts" + ] +} From 7f91f0ab5e4e475372bd80f4aee171702ac20d16 Mon Sep 17 00:00:00 2001 From: "Moo Yeol, Lee (Prescott)" Date: Wed, 8 May 2019 05:08:31 +0900 Subject: [PATCH 3/4] lint --- packages/lambda/src/launcher.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/lambda/src/launcher.ts b/packages/lambda/src/launcher.ts index 6f2d25fe..c32c12d6 100644 --- a/packages/lambda/src/launcher.ts +++ b/packages/lambda/src/launcher.ts @@ -7,9 +7,6 @@ many dependencies which complicates packaging of serverless services. */ -// Removed chrome process stdout/stderr file logging -// Remove responsibility of user data directory setup/teardown - import { ChildProcess, spawn } from "child_process"; import debug from "debug"; import * as net from "net"; From 5bd8455131777e052b268839fb0e91d3615de0ea Mon Sep 17 00:00:00 2001 From: "Moo Yeol, Lee (Prescott)" Date: Wed, 8 May 2019 05:12:06 +0900 Subject: [PATCH 4/4] remove faulty `--v=99` flag --- packages/lambda/src/launcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lambda/src/launcher.ts b/packages/lambda/src/launcher.ts index c32c12d6..d916f4ed 100644 --- a/packages/lambda/src/launcher.ts +++ b/packages/lambda/src/launcher.ts @@ -63,7 +63,7 @@ export default class LambdaChromeLauncher { this.startingUrl = startingUrl; this.chromePath = chromePath; this.chromeFlags = new Set([ - ...this.debug ? ["--enable-logging", "--log-level=0", "--v=99"] : [], + ...this.debug ? ["--enable-logging", "--log-level=0"] : [], ...DEFAULT_CHROME_FLAGS, `--remote-debugging-port=${this.port}`, "--disable-setuid-sandbox",