The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .circleci
    └── config.yml
├── .editorconfig
├── .github
    └── FUNDING.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── aws
    ├── ec2-spot-instance-specification.json
    ├── iam-serverless-chrome-automation-role-policy.json
    ├── iam-serverless-chrome-automation-user-policy.json
    └── user-data.sh
├── chrome
    └── README.md
├── docs
    ├── automation.md
    ├── chrome.md
    └── circleci.md
├── examples
    └── serverless-framework
    │   └── aws
    │       ├── README.md
    │       ├── package.json
    │       ├── serverless.yml
    │       ├── src
    │           ├── chrome
    │           │   ├── pdf.js
    │           │   ├── pdf.test.js
    │           │   ├── screenshot.js
    │           │   ├── screenshot.test.js
    │           │   └── version.js
    │           ├── handlers
    │           │   ├── pdf.js
    │           │   ├── pdf.test.js
    │           │   ├── requestLogger.js
    │           │   ├── screencast.js
    │           │   ├── screenshot.js
    │           │   ├── screenshot.test.js
    │           │   └── version.js
    │           └── utils
    │           │   ├── log.js
    │           │   └── sleep.js
    │       ├── webpack.config.js
    │       └── yarn.lock
├── package.json
├── packages
    ├── lambda
    │   ├── README.md
    │   ├── builds
    │   │   ├── chromium
    │   │   │   ├── Dockerfile
    │   │   │   ├── README.md
    │   │   │   ├── build
    │   │   │   │   ├── .gclient
    │   │   │   │   ├── Dockerfile
    │   │   │   │   └── build.sh
    │   │   │   ├── latest.sh
    │   │   │   └── version.json
    │   │   ├── firefox
    │   │   │   └── README.md
    │   │   └── nss
    │   │   │   └── README.md
    │   ├── chrome
    │   │   ├── chrome-headless-lambda-linux-59.0.3039.0.tar.gz
    │   │   ├── chrome-headless-lambda-linux-60.0.3089.0.tar.gz
    │   │   ├── chrome-headless-lambda-linux-60.0.3095.0.zip
    │   │   ├── chrome-headless-lambda-linux-x64.zip
    │   │   └── headless-chromium-64.0.3242.2-amazonlinux-2017-03.zip
    │   ├── index.d.ts
    │   ├── integration-test
    │   │   ├── dist
    │   │   ├── handler.js
    │   │   ├── package.json
    │   │   ├── serverless.yml
    │   │   └── yarn.lock
    │   ├── package.json
    │   ├── rollup.config.js
    │   ├── scripts
    │   │   ├── latest-versions.sh
    │   │   ├── package-binaries.sh
    │   │   ├── postinstall.js
    │   │   └── test-integration.sh
    │   ├── src
    │   │   ├── flags.js
    │   │   ├── index.js
    │   │   ├── index.test.js
    │   │   ├── launcher.js
    │   │   └── utils.js
    │   └── yarn.lock
    └── serverless-plugin
    │   ├── README.md
    │   ├── integration-test
    │       ├── .serverless_plugins
    │       │   └── serverless-plugin-chrome
    │       ├── package.json
    │       ├── serverless.yml
    │       ├── src
    │       │   ├── anotherHandler.js
    │       │   ├── handler.js
    │       │   ├── noChrome.js
    │       │   └── typescript-handler.ts
    │       └── yarn.lock
    │   ├── package.json
    │   ├── rollup.config.js
    │   ├── scripts
    │       └── test-integration.sh
    │   ├── src
    │       ├── constants.js
    │       ├── index.js
    │       ├── utils.js
    │       ├── utils.test.js
    │       └── wrapper-aws-nodejs.js
    │   └── yarn.lock
├── scripts
    ├── ci-daily.sh
    ├── docker-build-image.sh
    ├── docker-image-exists.sh
    ├── docker-login.sh
    ├── docker-pull-image.sh
    ├── ec2-build.sh
    ├── link-package.sh
    ├── release.sh
    ├── sync-package-versions.sh
    └── update-browser-versions.sh
└── yarn.lock


/.circleci/config.yml:
--------------------------------------------------------------------------------
  1 | version: 2
  2 | 
  3 | #
  4 | # Jobs
  5 | #
  6 | 
  7 | jobs:
  8 | 
  9 |   # This job builds the base project directory (e.g. ~/package.json)
 10 |   build:
 11 |     docker:
 12 |       - image: circleci/node:12
 13 |     steps:
 14 |       - checkout
 15 |       - restore_cache:
 16 |           key: dependency-cache-{{ checksum "package.json" }}
 17 |       - run: npm install
 18 |       - save_cache:
 19 |           key: dependency-cache-{{ checksum "package.json" }}
 20 |           paths:
 21 |             - node_modules
 22 | 
 23 |   # This job runs the lint tool on the whole repository
 24 |   lint:
 25 |     docker:
 26 |       - image: circleci/node:12
 27 |     steps:
 28 |       - checkout
 29 |       - restore_cache:
 30 |           key: dependency-cache-{{ checksum "package.json" }}
 31 |       - restore_cache:
 32 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}
 33 |       - restore_cache:
 34 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}-{{ checksum "packages/lambda/integration-test/package.json" }}             
 35 |       - restore_cache:
 36 |           key: dependency-cache-{{ checksum "packages/serverless-plugin/package.json" }}-{{ checksum "packages/serverless-plugin/integration-test/package.json" }}
 37 |       - restore_cache:
 38 |           key: dependency-cache-{{ checksum "examples/serverless-framework/aws/package.json" }}
 39 |       - restore_cache:
 40 |           key: build-cache-{{ .Revision }}-package-lambda
 41 |       # need to set permissions on the npm prefix so that we can npm link packages
 42 |       - run: sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
 43 |       - run: ./scripts/link-package.sh packages/serverless-plugin lambda               
 44 |       - run: npm run lint
 45 | 
 46 |   # This job runs all of the unit tests in the repository
 47 |   unit_test:
 48 |     docker:
 49 |       # Node 8 so we can avoid transpiling our tests
 50 |       - image: circleci/node:12
 51 |       # The unit tests require DevTools on localhost:9222
 52 |       - image: adieuadieu/headless-chromium-for-aws-lambda:stable
 53 |     steps:
 54 |       - checkout
 55 |       - restore_cache:
 56 |           key: dependency-cache-{{ checksum "package.json" }}
 57 |       - restore_cache:
 58 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}     
 59 |       - restore_cache:
 60 |           key: dependency-cache-{{ checksum "packages/serverless-plugin/package.json" }}-{{ checksum "packages/serverless-plugin/integration-test/package.json" }}
 61 |       - restore_cache:
 62 |           key: dependency-cache-{{ checksum "examples/serverless-framework/aws/package.json" }}
 63 |       - run:
 64 |           name: Install Chromium for local development Launcher test
 65 |           command: sudo apt-get install -y chromium
 66 |       - run: npm run ava
 67 | 
 68 |   # This job builds the @serverless-chrome/lambda package
 69 |   build_lambda:
 70 |     # use a machine because we extract binaries from Docker images  
 71 |     machine: true
 72 |     steps:
 73 |       - checkout
 74 |       - restore_cache:
 75 |           key: dependency-cache-{{ checksum "package.json" }}      
 76 |       - restore_cache:
 77 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}
 78 |       - run:
 79 |           name: Add chromium binary if missing
 80 |           command: |
 81 |             cd packages/lambda
 82 |             if [ ! -f "dist/headless-chromium" ]; then
 83 |               ./scripts/package-binaries.sh chromium stable
 84 |               cp dist/stable-headless-chromium dist/headless-chromium
 85 |             fi
 86 |       - run: cd packages/lambda && npm install
 87 |       - save_cache:
 88 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}
 89 |           paths:
 90 |             - packages/lambda/node_modules
 91 |             - packages/lambda/dist/headless-chromium
 92 |             - packages/lambda/dist/stable-headless-chromium
 93 |       - run: cd packages/lambda && npm run build
 94 |       - save_cache:
 95 |           key: build-cache-{{ .Revision }}-package-lambda
 96 |           paths:
 97 |             - packages/lambda/dist  
 98 |       - run: cd packages/lambda/integration-test && npm install      
 99 |       - save_cache:
100 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}-{{ checksum "packages/lambda/integration-test/package.json" }}
101 |           paths:
102 |             - packages/lambda/node_modules
103 |             - packages/lambda/integration-test/node_modules             
104 | 
105 |   # This job runs the @serverless-chrome/lambda package's integration tests
106 |   integration_test_lambda:
107 |     # use a machine because we run the integration tests with Docker (lambci/lambda)
108 |     machine: true
109 |     steps:
110 |       - checkout
111 |       - restore_cache:
112 |           key: dependency-cache-{{ checksum "package.json" }}      
113 |       - restore_cache:
114 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}
115 |       - restore_cache:
116 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}-{{ checksum "packages/lambda/integration-test/package.json" }}          
117 |       - restore_cache:
118 |           key: build-cache-{{ .Revision }}-package-lambda               
119 |       - run:
120 |           name: Integration test
121 |           command: cd packages/lambda && npm test
122 | 
123 |   # This job builds the serverless-plugin package
124 |   build_serverless_plugin:
125 |     docker:
126 |       - image: circleci/node:12
127 |     steps:
128 |       - checkout
129 |       - restore_cache:
130 |           key: dependency-cache-{{ checksum "package.json" }}      
131 |       - restore_cache:
132 |           key: dependency-cache-{{ checksum "packages/serverless-plugin/package.json" }}-{{ checksum "packages/serverless-plugin/integration-test/package.json" }}
133 |       - restore_cache:
134 |           key: build-cache-{{ .Revision }}-package-lambda
135 |       # need to set permissions on the npm prefix so that we can npm link packages
136 |       - run: sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
137 |       - run: ./scripts/link-package.sh packages/serverless-plugin lambda
138 |       - run: cd packages/serverless-plugin && npm install
139 |       - run: cd packages/serverless-plugin && npm run build
140 |       - save_cache:
141 |           key: build-cache-{{ .Revision }}-package-serverless-plugin
142 |           paths:
143 |             - packages/serverless-plugin/dist         
144 |       - run: |
145 |           [ -z "$CIRCLE_TAG" ] || ./scripts/link-package.sh packages/serverless-plugin/integration-test lambda
146 |       - run: cd packages/serverless-plugin/integration-test && npm install      
147 |       - save_cache:
148 |           key: dependency-cache-{{ checksum "packages/serverless-plugin/package.json" }}-{{ checksum "packages/serverless-plugin/integration-test/package.json" }}
149 |           paths:
150 |             - packages/serverless-plugin/node_modules
151 |             - packages/serverless-plugin/integration-test/node_modules           
152 | 
153 |   # This job runs the serverless-plugin package's integration tests
154 |   integration_test_serverless_plugin:
155 |     # use a machine because we run the integration tests with Docker (lambci/lambda)
156 |     machine: true
157 |     steps:
158 |       - checkout
159 |       - restore_cache:
160 |           key: dependency-cache-{{ checksum "package.json" }}      
161 |       - restore_cache:
162 |           key: dependency-cache-{{ checksum "packages/serverless-plugin/package.json" }}-{{ checksum "packages/serverless-plugin/integration-test/package.json" }}
163 |       - restore_cache:
164 |           key: build-cache-{{ .Revision }}-package-lambda
165 |       - run:
166 |           name: Integration test
167 |           command: cd packages/serverless-plugin && npm test
168 | 
169 |   # This job builds the serverless-framework AWS example service
170 |   build_serverless_example:
171 |     docker:
172 |       - image: circleci/node:12
173 |     steps:
174 |       - checkout
175 |       - restore_cache:
176 |           key: dependency-cache-{{ checksum "package.json" }}      
177 |       - restore_cache:
178 |           key: dependency-cache-{{ checksum "examples/serverless-framework/aws/package.json" }}
179 |       - restore_cache:
180 |           key: build-cache-{{ .Revision }}-package-lambda
181 |       - restore_cache:
182 |           key: build-cache-{{ .Revision }}-package-serverless-plugin          
183 |       # need to set permissions on the npm prefix so that we can npm link packages
184 |       - run: sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
185 |       - run: ./scripts/link-package.sh packages/serverless-plugin lambda                
186 |       - run: ./scripts/link-package.sh examples/serverless-framework/aws serverless-plugin          
187 |       - run: cd examples/serverless-framework/aws && npm install
188 |       - save_cache:
189 |           key: dependency-cache-{{ checksum "examples/serverless-framework/aws/package.json" }}
190 |           paths:
191 |             - examples/serverless-framework/aws/node_modules      
192 | 
193 |   # This job checks for new versions of browsers (chromium)
194 |   # and updates the repository code when new docker images have been built
195 |   update_browser_versions:
196 |     docker:
197 |       - image: circleci/node:12
198 |     branch:
199 |       - master
200 |     steps:
201 |       - checkout
202 |       - run: scripts/update-browser-versions.sh
203 | 
204 |   # This job handles release automation. Usually run when new binaries of
205 |   # browsers have been built. Only stable-channel binaries will trigger a git tag
206 |   # from which we make releases.
207 |   release:
208 |     # use a machine because we run Docker containers while preparing the release
209 |     machine:
210 |       image: ubuntu-2004:202010-01
211 |     branch:
212 |       - master
213 |     steps:
214 |       - checkout
215 |       - restore_cache:
216 |           key: dependency-cache-{{ checksum "package.json" }}
217 |       - restore_cache:
218 |           key: dependency-cache-{{ checksum "packages/lambda/package.json" }}
219 |       - restore_cache:
220 |           key: dependency-cache-{{ checksum "packages/serverless-plugin/package.json" }}-{{ checksum "packages/serverless-plugin/integration-test/package.json" }}
221 |       # we need a newer version of NPM than is available by default, e.g. >=4
222 |       # because we make use of the "prepublishOnly" package.json script, which was introduces in npm@4
223 |       # - run:
224 |       #     name: Update NPM
225 |       #     command: npm install -g npm@latest
226 |       - run: scripts/release.sh
227 | 
228 |   # This step triggers builds of binaries when new versions are available
229 |   # The builds happen on AWS Spot Instances created by this step and are not run
230 |   # on CircleCI due to instance-size/build-time constraints there.
231 |   # AWS Spot Instances are configured by ~/aws/ec2-spot-instance-specification.json.
232 |   # On start up, the spot instances run the user-data script in ~/aws/user-data.sh.
233 |   # Using "amazonlinux:latest" image because it's easy to install aws-cli.
234 |   build_new_binaries:
235 |     docker:
236 |       - image: amazonlinux:latest
237 |     branch:
238 |       - master
239 |     steps:
240 |       - checkout
241 |       - run:
242 |           name: Install AWS CLI & JQ
243 |           command: yum install -y aws-cli jq
244 |       - run: 
245 |           name: Build stable-channel Chromium
246 |           command: scripts/ci-daily.sh stable chromium
247 |       - run: 
248 |           name: Build beta-channel Chromium
249 |           command: scripts/ci-daily.sh beta chromium
250 |       - run: 
251 |           name: Build dev-channel Chromium
252 |           command: scripts/ci-daily.sh dev chromium
253 | 
254 | 
255 | #
256 | # Workflows
257 | #
258 | 
259 | workflows:
260 |   version: 2
261 | 
262 |   # Runs on every commit. The jobs install and build dependencies
263 |   # and also setup test environments and prerequisites for integration tests
264 |   # On tagged commits on master branch, the "release" job automates publishing 
265 |   # of NPM packages and making a GitHub release. The release and npm packages are published
266 |   # by a bot account (botsimo).
267 |   build_test_release:
268 |     jobs:
269 |       - build     
270 | 
271 |       - build_lambda:     
272 |           requires:
273 |             - build
274 |       - build_serverless_plugin:    
275 |           requires:
276 |             - build_lambda
277 |       - build_serverless_example:     
278 |           requires:
279 |             - build_serverless_plugin
280 |       - lint:      
281 |           requires:
282 |             - build
283 |             - build_lambda
284 |             - build_serverless_plugin
285 |             - build_serverless_example
286 |       - unit_test:     
287 |           requires:
288 |             - build
289 |             - build_lambda
290 |             - build_serverless_plugin
291 |             - build_serverless_example
292 |       - integration_test_lambda:      
293 |           requires:
294 |             - build_lambda
295 |       - integration_test_serverless_plugin:    
296 |           requires:
297 |             - build_serverless_plugin
298 |       - release:
299 |           filters:
300 |             branches:
301 |               only: master
302 |           requires:
303 |             - lint
304 |             - unit_test
305 |             - integration_test_lambda
306 |             - integration_test_serverless_plugin
307 | 
308 |   # Runs daily at 08:00 UTC. The job checks for new versions of
309 |   # headless browsers (chromium) and creates an AWS spot-instance-request
310 |   # on which to compile/build any new versions.
311 |   # Takes about 2h10m for Chromium 64+ on c5.2xlarge
312 |   daily_build:
313 |     triggers:
314 |       - schedule:
315 |           cron: "0 8 * * *"
316 |           filters:
317 |             branches:
318 |               only:
319 |                 - master
320 |     jobs:
321 |       - build_new_binaries
322 | 
323 |   # Runs daily at 11:00 UTC. The job checks if any new versions of
324 |   # headless browsers (chromium) have been built and updates the repository
325 |   # code to point at any new versions. A new stable-channel version will
326 |   # trigger an automated release in the "release" job in the build_test_release workflow
327 |   daily_version_update:
328 |     triggers:
329 |       - schedule:
330 |           cron: "0 11 * * *"
331 |           filters:
332 |             branches:
333 |               only:
334 |                 - master
335 |     jobs:
336 |       - update_browser_versions
337 | 


--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
 1 | [*]
 2 | indent_style = space
 3 | end_of_line = lf
 4 | indent_size = 2
 5 | charset = utf-8
 6 | trim_trailing_whitespace = true
 7 | 
 8 | [*.md]
 9 | max_line_length = 0
10 | trim_trailing_whitespace = false
11 | 


--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | 
3 | github: adieuadieu # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | custom: # Replace with a single custom sponsorship URL
9 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | # Logs
 2 | logs
 3 | *.log
 4 | npm-debug.log*
 5 | 
 6 | # Runtime data
 7 | pids
 8 | *.pid
 9 | *.seed
10 | 
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 | 
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 | 
17 | # nyc test coverage
18 | .nyc_output
19 | 
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 | 
23 | # node-waf configuration
24 | .lock-wscript
25 | 
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 | 
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 | 
33 | # Optional npm cache directory
34 | .npm
35 | 
36 | # Optional REPL history
37 | .node_repl_history
38 | 
39 | # package directories
40 | node_modules
41 | jspm_packages
42 | 
43 | # Serverless directories
44 | .serverless
45 | 
46 | 
47 | *.log
48 | .DS_Store
49 | .nyc_output
50 | .serverless
51 | .webpack
52 | coverage
53 | node_modules
54 | .idea/
55 | dist/
56 | *.iml
57 | webpack-assets.json
58 | webpack-stats.json
59 | npm-debug.log
60 | awsconfig.json
61 | npm-debug.log
62 | dump/
63 | config.json
64 | /config.js
65 | event.json
66 | .build/
67 | headless-chromium-amazonlinux-2017-03.zip
68 | .eslintcache
69 | packages/lambda/integration-test/headless-chromium
70 | 
71 | .env
72 | 


--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # package directories
2 | node_modules
3 | jspm_packages
4 | 
5 | # Serverless directories
6 | .serverless


--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .nyc_output/
2 | dist/
3 | package.json
4 | package-lock.json
5 | 


--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
 1 | # Change Log
 2 | All notable changes to this project will be documented in this file.
 3 | 
 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
 5 | and this project adheres to [Semantic Versioning](http://semver.org/).
 6 | 
 7 | 
 8 | ## [Unreleased]
 9 | ### Added
10 | - Binary support in AWS Lambda/API Gateway example
11 | - Build and release tooling shell scripts and Dockerfile's
12 | - Integration tests and CircleCI setup
13 | - Complete automation of build/test/release workflows
14 | - serverless-plugin-chrome: support for limiting Chrome to only select service functions with the `custom.chrome.functions` parameter.
15 | - @serverless-chrome/lambda NPM package
16 | - serverless-plugin-chrome NPM package for Serverless-framework
17 | - Lots of new and updated documentation
18 | - CHANGELOG.md.
19 | 
20 | ### Changed
21 | - example Serverless-framework printToPdf function handler to use the Serverless plugin
22 | - example Serverless-framework captureScreenshot function handler to use the Serverless plugin
23 | 
24 | 
25 | ## [0.5.0] - 2017-03-11, 2017-05-09
26 | ### Added
27 | - Headless Chrome headless_shell binary version 60.0.3089.0 built for AWS Lambda
28 | - Serverless-framework configuration for deploying to AWS Lambda
29 | - sample printToPdf Lambda function handler
30 | - sample captureScreenshot Lambda function handler
31 | - Initial documentation in README.md
32 | 


--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
 1 | # Contributor Covenant Code of Conduct
 2 | 
 3 | ## Our Pledge
 4 | 
 5 | In the interest of fostering an open and welcoming environment, we as
 6 | contributors and maintainers pledge to making participation in our project and
 7 | our community a harassment-free experience for everyone, regardless of age, body
 8 | size, disability, ethnicity, gender identity and expression, level of experience,
 9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 | 
12 | ## Our Standards
13 | 
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 | 
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 | 
23 | Examples of unacceptable behavior by participants include:
24 | 
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 |   address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 |   professional setting
33 | 
34 | ## Our Responsibilities
35 | 
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 | 
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 | 
46 | ## Scope
47 | 
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 | 
55 | ## Enforcement
56 | 
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project owner, Marco Lüthy. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 | 
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 | 
68 | ## Attribution
69 | 
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 | 
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
 1 | # Contributing to this project
 2 | 
 3 | [fork]: https://github.com/adieuadieu/serverless-chrome/fork
 4 | [pr]: https://github.com/adieuadieu/serverless-chrome/compare
 5 | [code-of-conduct]: CODE_OF_CONDUCT.md
 6 | 
 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
 8 | 
 9 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
10 | 
11 | ## Contribution Agreement
12 | 
13 | As a contributor, you represent that the code you submit is your original work or that of your employer (in which case you represent you have the right to bind your employer). By submitting code, you (and, if applicable, your employer) are licensing the submitted code to the open source community subject to the MIT license.
14 | 
15 | 
16 | ## Submitting a pull request
17 | 
18 | Please branch from and raise PRs against the `develop` branch.
19 | 
20 | 0. [Fork][fork] and clone the repository
21 | 0. Create a new branch: `git checkout -b feature/my-new-feature-name develop`
22 | 0. Make your change
23 | 0. Run the unit tests and make sure they pass and have 100% coverage.
24 | 0. Push to your fork and [submit a pull request][pr] to merge your changes.
25 | 0. Pat your self on the back and wait for your pull request to be reviewed and merged.
26 | 
27 | Here are a few things you can do that will increase the likelihood of your pull request being accepted:
28 | 
29 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, please submit them as separate pull requests.
30 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
31 | - In your pull request description, provide as much detail as possible. This context helps the reviewer to understand the motivation for and impact of the change.
32 | - Make sure that all the unit tests still pass. PRs with failing tests won't be merged.
33 | 
34 | ## Resources
35 | 
36 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
37 | - [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/)
38 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
39 | - [GitHub Help](https://help.github.com)
40 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2017-2018 Marco Lüthy
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # serverless-chrome
  2 | 
  3 | Serverless Chrome contains everything you need to get started running headless
  4 | Chrome on AWS Lambda (possibly Azure and GCP Functions soon).
  5 | 
  6 | The aim of this project is to provide the scaffolding for using Headless Chrome
  7 | during a serverless function invocation. Serverless Chrome takes care of
  8 | building and bundling the Chrome binaries and making sure Chrome is running when
  9 | your serverless function executes. In addition, this project also provides a few
 10 | example services for common patterns (e.g. taking a screenshot of a page,
 11 | printing to PDF, some scraping, etc.)
 12 | 
 13 | Why? Because it's neat. It also opens up interesting possibilities for using the
 14 | [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/tot/)
 15 | (and tools like [Chromeless](https://github.com/graphcool/chromeless) or
 16 | [Puppeteer](https://github.com/GoogleChrome/puppeteer)) in serverless
 17 | architectures and doing testing/CI, web-scraping, pre-rendering, etc.
 18 | 
 19 | [![CircleCI](https://img.shields.io/circleci/project/github/adieuadieu/serverless-chrome/master.svg?style=flat-square)](https://circleci.com/gh/adieuadieu/serverless-chrome)
 20 | [![David](https://img.shields.io/david/adieuadieu/serverless-chrome.svg?style=flat-square)]()
 21 | [![David](https://img.shields.io/david/dev/adieuadieu/serverless-chrome.svg?style=flat-square)]()
 22 | [![GitHub release](https://img.shields.io/github/release/adieuadieu/serverless-chrome.svg?style=flat-square)](https://github.com/adieuadieu/serverless-chrome)
 23 | 
 24 | ## Contents
 25 | 
 26 | 1. [Quick Start](#quick-start)
 27 | 1. [The Project](#the-project)
 28 | 1. [Examples](#examples)
 29 | 1. [Documentation & Resources](#documentation--resources)
 30 |    1. [Building Headless Chrome/Chromium](#building-headless-chromechromium)
 31 | 1. [Testing](#testing)
 32 | 1. [Articles & Tutorials](#articles--tutorials)
 33 | 1. [Troubleshooting](#troubleshooting)
 34 | 1. [Roadmap](#roadmap)
 35 | 1. [Projects & Companies using serverless-chrome](#projects--companies-using-serverless-chrome)
 36 | 1. [Change log](#change-log)
 37 | 1. [Contributing](#contributing)
 38 | 1. [Prior Art](#prior-art)
 39 | 1. [License](#license)
 40 | 
 41 | ## Quick Start
 42 | 
 43 | "Bla bla bla! I just want to start coding!" No problem:
 44 | 
 45 | Using AWS Lambda, the quickest way to get started is with the
 46 | [Serverless-framework](https://serverless.com/) CLI.
 47 | 
 48 | First, install `serverless` globally (`npm install -g serverless`) and then:
 49 | 
 50 | ```bash
 51 | serverless create -u https://github.com/adieuadieu/serverless-chrome/tree/master/examples/serverless-framework/aws
 52 | ```
 53 | 
 54 | Then, you must configure your AWS credentials either by defining
 55 | `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environmental variables, or
 56 | using an AWS profile. You can read more about this on the
 57 | [Serverless Credentials Guide](https://serverless.com/framework/docs/providers/aws/guide/credentials/).
 58 | 
 59 | In short, either:
 60 | 
 61 | ```bash
 62 | export AWS_PROFILE=<your-profile-name>
 63 | ```
 64 | 
 65 | or
 66 | 
 67 | ```bash
 68 | export AWS_ACCESS_KEY_ID=<your-key-here>
 69 | export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>
 70 | ```
 71 | 
 72 | Then, to deploy the service and all of its functions:
 73 | 
 74 | ```bash
 75 | npm run deploy
 76 | ```
 77 | 
 78 | Further details are available in the
 79 | [Serverless Lambda example](examples/serverless-framework/aws).
 80 | 
 81 | ## The Project
 82 | 
 83 | This project contains:
 84 | 
 85 | * **[@serverless-chrome/lambda](packages/lambda)** NPM package<br/> A standalone
 86 |   module for AWS Lambda which bundles and launches Headless Chrome with support
 87 |   for local development. For use with—but not limited to—tools like
 88 |   [Apex](https://github.com/apex/apex),
 89 |   [Claudia.js](https://github.com/claudiajs/claudia),
 90 |   [SAM Local](https://github.com/awslabs/aws-sam-local), or
 91 |   [Serverless](https://serverless.com/).
 92 | * **[serverless-plugin-chrome](packages/serverless-plugin)** NPM package<br/> A
 93 |   plugin for [Serverless-framework](https://serverless.com/) services which
 94 |   takes care of everything for you. You just write the code to drive Chrome.
 95 | * **[Example functions](examples/)**
 96 |   * [Serverless-framework](https://serverless.com/) AWS Lambda Node.js functions
 97 |     using `serverless-plugin-chrome`
 98 | * **[Build Automation](docs/automation.md) &
 99 |   [CI/CD](.circleci/config.yml)**<br/> Build and release tooling shell scripts
100 |   and Dockerfile for automating the build/release of headless Chrome for
101 |   serverless environments (AWS Lambda).
102 | 
103 | ## Examples
104 | 
105 | A collection of example functions for different providers and frameworks.
106 | 
107 | ### Serverless-framework
108 | 
109 | * [Serverless-framework](examples/serverless-framework/aws) Some simple
110 |   functions for the [Serverless-framework](https://serverless.com/) on AWS
111 |   Lambda. It includes the following example functions:
112 | 
113 |   * Print to PDF
114 |   * Capture Screenshot
115 |   * Page-load Request Logger
116 | 
117 | ## Documentation & Resources
118 | 
119 | ### Building Headless Chrome/Chromium
120 | 
121 | * Automated, regularly prebuilt binaries can be found on the
122 |   [Releases](https://github.com/adieuadieu/serverless-chrome/releases) page 😎
123 | * [adieuadieu/headless-chromium-for-aws-lambda](https://hub.docker.com/r/adieuadieu/headless-chromium-for-aws-lambda/)
124 |   Docker image
125 | * [Documentation on building your own binaries](/docs/chrome.md)
126 | * [Medium article on how to do build from scratch](https://medium.com/@marco.luethy/running-headless-chrome-on-aws-lambda-fa82ad33a9eb).
127 |   This was the origin of this project.
128 | 
129 | ## Testing
130 | 
131 | Test with `npm test`. Each package also contains it's own integration tests
132 | which can be run with `npm run test:integration`.
133 | 
134 | ## Articles & Tutorials
135 | 
136 | A collection of articles and tutorials written by others on using serverless-chrome
137 | 
138 | * [AWS DevOps Blog — UI Testing at Scale with AWS Lambda](https://aws.amazon.com/blogs/devops/ui-testing-at-scale-with-aws-lambda/)
139 | * [Running puppeteer and headless chrome on AWS lambda with Serverless](https://nadeeshacabral.com/writing/2018/running-puppeteer-and-headless-chrome-on-aws-lambda-with-serverless)
140 | * [Will it blend? Or how to run Google Chrome in AWS Lambda](https://medium.freecodecamp.org/will-it-blend-or-how-to-run-google-chrome-in-aws-lambda-2c960fee8b74)
141 | * [Running Selenium and Headless Chrome on AWS Lambda](https://medium.com/clog/running-selenium-and-headless-chrome-on-aws-lambda-fb350458e4df)
142 | * [AWS Lambda上のheadless chromeをPythonで動かす](https://qiita.com/nabehide/items/754eb7b7e9fff9a1047d)
143 | * [AWS Lambda上でpuppeteerを動かして、スクレイピングする](https://qiita.com/chimame/items/04c9b45d8467cf32892f)
144 | * [serverless-chrome で日本語を表示できるようにする](http://fd0.hatenablog.jp/entry/2017/09/10/223042)
145 | 
146 | ## Troubleshooting
147 | 
148 | <details id="troubleshooting-1">
149 |   <summary>Can't get Selenium / ChromeDriver to work</summary>
150 |   Make sure that the versions of serverless-chrome, chromedriver, and Selenium are compatible. More details in [#133](https://github.com/adieuadieu/serverless-chrome/issues/133#issuecomment-382743975).
151 | </details>
152 | 
153 | ## Roadmap
154 | 
155 | _1.1_
156 | 
157 | 1. Support for Google Cloud Functions
158 | 1. Example for Apex
159 | 1. Example for Claudia.js
160 | 
161 | _1.2_
162 | 
163 | 1. DOM manipulation and scraping example handler
164 | 
165 | _Future_
166 | 
167 | 1. Support for Azure Functions
168 | 1. Headless Firefox
169 | 
170 | ## Projects & Companies using serverless-chrome
171 | 
172 | Tell us about your project on the
173 | [Wiki](https://github.com/adieuadieu/serverless-chrome/wiki/Projects-&-Companies-Using-serverless-chrome)!
174 | 
175 | ## Change log
176 | 
177 | See the [CHANGELOG](CHANGELOG.md)
178 | 
179 | ## Contributing
180 | 
181 | OMG. Yes. Plz, [halp meeee](/CONTRIBUTING.md).
182 | 
183 | ## Prior Art
184 | 
185 | This project was inspired in various ways by the following projects:
186 | 
187 | * [PhantomJS](http://phantomjs.org/)
188 | * [wkhtmltopdf](https://github.com/wkhtmltopdf/wkhtmltopdf)
189 | * [node-webkitgtk](https://github.com/kapouer/node-webkitgtk)
190 | * [electron-pdf](https://github.com/Janpot/electron-pdf)
191 | 
192 | ## License
193 | 
194 | **serverless-chrome** © [Marco Lüthy](https://github.com/adieuadieu). Released under the [MIT](./LICENSE) license.<br>
195 | Authored and maintained by Marco Lüthy with help from [contributors](https://github.com/adieuadieu/serverless-chrome/contributors).
196 | 
197 | > [github.com/adieuadieu](https://github.com/adieuadieu) · GitHub [@adieuadieu](https://github.com/adieuadieu) · Twitter [@adieuadieu](https://twitter.com/adieuadieu) · Medium [@marco.luethy](https://medium.com/@marco.luethy)
198 | 


--------------------------------------------------------------------------------
/aws/ec2-spot-instance-specification.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "SpotPrice": "0.34",
 3 |   "InstanceCount": 1,
 4 |   "Type": "one-time",
 5 |   "LaunchSpecification": {
 6 |     "ImageId": "ami-8c1be5f6",
 7 |     "InstanceType": "c4.2xlarge",
 8 |     "IamInstanceProfile": {
 9 |       "Arn": "this gets updated by the ec2-build.sh script"
10 |     },
11 |     "BlockDeviceMappings": [
12 |       {
13 |         "DeviceName": "/dev/xvda",
14 |         "Ebs": {
15 |           "DeleteOnTermination": true,
16 |           "VolumeType": "gp2",
17 |           "VolumeSize": 64,
18 |           "SnapshotId": "snap-080eb3cb2eda29974"
19 |         }
20 |       }
21 |     ],
22 |     "UserData": ""
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/aws/iam-serverless-chrome-automation-role-policy.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "Version": "2012-10-17",
 3 |   "Statement": [
 4 |     {
 5 |       "Effect": "Allow",
 6 |       "Action": [
 7 |         "ssm:GetParameter",
 8 |         "ssm:GetParameters"
 9 |       ],
10 |       "Resource": [
11 |         "arn:aws:ssm:*:*:parameter/serverless-chrome-automation/*"
12 |       ]
13 |     },
14 |     {
15 |       "Effect": "Allow",
16 |       "Action": [
17 |         "logs:CreateLogGroup",
18 |         "logs:CreateLogStream",
19 |         "logs:PutLogEvents",
20 |         "logs:DescribeLogStreams"
21 |       ],
22 |       "Resource": [
23 |         "arn:aws:logs:*:*:log-group:/serverless-chrome-automation:log-stream:*"
24 |       ]
25 |     },
26 |     {
27 |       "Effect": "Allow",
28 |       "Action": [
29 |         "s3:GetObject",
30 |         "s3:PutObject"
31 |       ],
32 |       "Resource": [
33 |         "arn:aws:s3:::serverless-chrome-binaries/*"
34 |       ]
35 |     }
36 |   ]
37 | }
38 | 


--------------------------------------------------------------------------------
/aws/iam-serverless-chrome-automation-user-policy.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "Version": "2012-10-17",
 3 |   "Statement": [
 4 |     {
 5 |       "Sid": "Stmt1510407541000",
 6 |       "Effect": "Allow",
 7 |       "Action": [
 8 |         "ec2:RequestSpotInstances",
 9 |         "iam:PassRole"
10 |       ],
11 |       "Resource": [
12 |         "*"
13 |       ]
14 |     }
15 |   ]
16 | }
17 | 


--------------------------------------------------------------------------------
/aws/user-data.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Usage: Run as a start-up script on an EC2 instance via user-data cloud-init
 6 | # ref: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
 7 | #
 8 | 
 9 | # These get replaced with real values in ~/scripts/ec2-build.sh
10 | BROWSER=INSERT_BROWSER_HERE
11 | CHANNEL=INSERT_CHANNEL_HERE
12 | VERSION=INSERT_VERSION_HERE
13 | DOCKER_ORG=INSERT_DOCKER_ORG_HERE
14 | S3_BUCKET=INSERT_S3_BUCKET_HERE
15 | FORCE_NEW_BUILD=INSERT_FORCE_NEW_BUILD_HERE
16 | 
17 | echo "Starting user-data script. $BROWSER $VERSION ($CHANNEL channel)"
18 | 
19 | # 
20 | # Setup CloudWatch logging
21 | # ref: http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/QuickStartEC2Instance.html
22 | #
23 | yum install -y --quiet awslogs
24 | 
25 | # config ref: http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html
26 | printf "
27 | [cloudinit]
28 | log_group_name = /serverless-chrome-automation
29 | log_stream_name = {instance_id}-cloudinit-%s-%s-%s
30 | file = /var/log/cloud-init-output.log
31 |   " \
32 |   "$BROWSER" "$VERSION" "$CHANNEL" >> /etc/awslogs/awslogs.conf
33 | 
34 | service awslogs start
35 | 
36 | # 
37 | # Go time (if brower and release channel are set.)
38 | #
39 | if [ -n "$CHANNEL" ] && [ -n "$BROWSER" ]; then
40 |   yum update -y --quiet
41 | 
42 |   yum install -y --quiet docker jq git
43 | 
44 |   service docker start
45 | 
46 |   # EC2_INSTANCE_ID=$(curl -s http://instance-data/latest/meta-data/instance-id)
47 | 
48 |   AWS_REGION=$(curl -s http://instance-data/latest/dynamic/instance-identity/document | \
49 |     jq -r ".region" \
50 |   )
51 | 
52 |   DOCKER_USER=$(aws ssm get-parameter \
53 |     --region "$AWS_REGION" \
54 |     --with-decryption \
55 |     --name /serverless-chrome-automation/DOCKER_USER | \
56 |     jq -r ".Parameter.Value" \
57 |   )
58 | 
59 |   DOCKER_PASS=$(aws ssm get-parameter \
60 |     --region "$AWS_REGION" \
61 |     --with-decryption \
62 |     --name /serverless-chrome-automation/DOCKER_PASS | \
63 |     jq -r ".Parameter.Value" \
64 |   )
65 | 
66 |   export AWS_REGION
67 |   export DOCKER_USER
68 |   export DOCKER_PASS
69 |   export DOCKER_ORG
70 |   export S3_BUCKET
71 |   export FORCE_NEW_BUILD
72 |   
73 |   git clone "https://github.com/adieuadieu/serverless-chrome.git"
74 | 
75 |   cd serverless-chrome || return
76 | 
77 |   # git checkout develop # in case you want to build develop branch
78 | 
79 |   ./scripts/docker-build-image.sh "$CHANNEL" "$BROWSER" "$VERSION"
80 | fi
81 | 
82 | # 
83 | # Shutdown (terminate) the instance
84 | #
85 | 
86 | echo "User-data script completed. Shutting down instance.."
87 | 
88 | uptime
89 | 
90 | # Don't shut down immediately so that CloudWatch Agent has time to push logs to AWS
91 | shutdown -h -t 10 +1
92 | 


--------------------------------------------------------------------------------
/chrome/README.md:
--------------------------------------------------------------------------------
1 | Hi there. This file has moved [here](/docs/chrome.md).
2 | 


--------------------------------------------------------------------------------
/docs/automation.md:
--------------------------------------------------------------------------------
 1 | # Automation
 2 | 
 3 | These docs are a work-in-progress (read: incomplete).
 4 | 
 5 | Automation of builds.
 6 | 
 7 | There is more documentation concerning building Chrome/Chromium [here](/docs/chrome.md)
 8 | 
 9 | 
10 | ## Setup
11 | 
12 | - create IAM role "serverless-chrome-automation" with policy defined in `aws/iam-serverless-chrome-automation-role.json`
13 | - if desired, modify `aws/ec2-spot-instance-specification.json` to change instance-types and max spot-price 
14 | - AWS_ACCESS_KEY_ID
15 | - AWS_SECRET_ACCESS_KEY
16 | - AWS_IAM_INSTANCE_ARN
17 | 
18 | 
19 | ## Manual Build
20 | 
21 | To perform a manual build on EC2 using a spot instance:
22 | 
23 | ```sh
24 | ./scripts/ec2-build.sh chromium stable 62.0.3202.75
25 | ```
26 | 
27 | 
28 | ## CI Build
29 | 
30 | See `.circleci/config.yml` "daily" workflow for example.
31 | 
32 | Example: Build latest Chromium (stable channel):
33 | 
34 | ```sh
35 | ./scripts/ci-daily.sh stable chromium
36 | ```
37 | 


--------------------------------------------------------------------------------
/docs/chrome.md:
--------------------------------------------------------------------------------
  1 | # Chrome/Chromium on AWS Lambda
  2 | 
  3 | ## Contents
  4 | 1. [Prebuilt Binaries](#prebuilt-binaries)
  5 | 1. [Docker Image](#docker-image)
  6 | 1. [Build Yourself](#build-yourself)
  7 |     1. [Locally](#locally)
  8 |     1. [With AWS EC2](#with-aws-ec2)
  9 | 1. [Fonts](#fonts)
 10 | 1. [Known Issues / Limitations](#known-issues--limitations)
 11 | 
 12 | 
 13 | ## Prebuilt Binaries
 14 | 
 15 | Prebuilt binaries are regularly released and made available under [Releases](https://github.com/adieuadieu/serverless-chrome/releases). These binaries have been checked to work within the Lambda Execution Environment. New binaries are released whenever there's a new [stable-channel version](https://omahaproxy.appspot.com/) for Linux (about once every 1-2 weeks).
 16 | 
 17 | Check this project's released binaries against the latest with:
 18 | 
 19 | ```bash
 20 | ./packages/lambda/scripts/latest-versions.sh
 21 | ```
 22 | 
 23 | 
 24 | ## Docker Image
 25 | 
 26 | The prebuild binaries made available under [Releases](https://github.com/adieuadieu/serverless-chrome/releases) are extracted from the public [adieuadieu/headless-chromium-for-aws-lambda](https://hub.docker.com/r/adieuadieu/headless-chromium-for-aws-lambda/) Docker image. This image is updated daily when there's a new Chromium version on any channel (stable, beta, dev).
 27 | 
 28 | The image uses [lambci/lambda](https://hub.docker.com/r/lambci/lambda/) as a base which very closely mimics the live AWS Lambda Environment. We use the [adieuadieu/headless-chromium-for-aws-lambda](https://hub.docker.com/r/adieuadieu/headless-chromium-for-aws-lambda/) image for unit and integration tests.
 29 | 
 30 | Run it yourself with:
 31 | 
 32 | ```bash
 33 | docker run -d --rm \
 34 |   --name headless-chromium \
 35 |   -p 9222:9222 \
 36 |   adieuadieu/headless-chromium-for-aws-lambda
 37 | ```
 38 | 
 39 | Headless Chromium is now running and accessible:
 40 | 
 41 | ```
 42 | GET http://localhost:9222/
 43 | ```
 44 | 
 45 | Extract the headless Chrome binary from the container with:
 46 | 
 47 | ```bash
 48 | docker run -dt --rm --name headless-chromium adieuadieu/headless-chromium-for-aws-lambda:stable
 49 | docker cp headless-chromium:/bin/headless-chromium ./
 50 | docker stop headless-chromium
 51 | ```
 52 | 
 53 | 
 54 | ## Build Yourself
 55 | 
 56 | ### Locally
 57 | 
 58 | The easiest way to build headless Chromium locally is with Docker:
 59 | 
 60 | ```bash
 61 | cd packages/lambda/builds/chromium
 62 | 
 63 | export CHROMIUM_VERSION=$(./latest.sh stable)
 64 | 
 65 | docker build \
 66 |   -t "headless-chromium:$CHROMIUM_VERSION" \
 67 |   --build-arg VERSION="$CHROMIUM_VERSION" \
 68 |   "build"
 69 | ```
 70 | 
 71 | The script `./packages/lambda/builds/chromium/latest.sh stable` returns the latest "stable" channel version of Chromium, e.g. "62.0.3202.94".
 72 | 
 73 | The [Dockerfile](/packages/lambda/builds/chromium/build/Dockerfile) in [`packages/lambda/builds/chromium/build`](/packages/lambda/builds/chromium/build) builds the Chromium version specified by `CHROMIUM_VERSION` with the [`build.sh`](/packages/lambda/builds/chromium/build/build.sh) script.
 74 | 
 75 | It's also possible to build Chromium without Docker using just the [`build.sh`](/packages/lambda/builds/chromium/build/build.sh) script. However, make sure that you run the script as `root` on a compatible OS environment (e.g. AmazonLinux on EC2):
 76 | 
 77 | ```bash
 78 | cd packages/lambda/builds/chromium
 79 | 
 80 | export VERSION=$(./latest.sh stable)
 81 | 
 82 | ./build/build.sh
 83 | ```
 84 | 
 85 | **Note:** On MacOS building with Docker, if you're running into a no-more-disk-space-available error, you may need to [increase the size](https://community.hortonworks.com/articles/65901/how-to-increase-the-size-of-the-base-docker-for-ma.html) of the Docker data sparse image. *Warning!:* This will wipe out all of your local images/containers:
 86 | 
 87 | ```bash
 88 | rm ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2
 89 | qemu-img create -f qcow2 ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2 50G
 90 | ```
 91 | 
 92 | Install `qemu-img` with `brew install qemu`
 93 | 
 94 | 
 95 | ### With AWS EC2
 96 | 
 97 | How the cool kids build.
 98 | 
 99 | Easily build Chromium using an EC2 Spot Instance (spot-block) using the [`ec2-build.sh`](/scripts/ec2-build.sh) script. With a `c5.2xlarge` spot-instance a single build takes rougly 2h15m and usually costs between $0.25 and $0.30 in `us-east-1a`. Or, ~30m on `c5.18xlarge` for about $0.50. To build Chromium, an instance with at least 4GB of memory is required.
100 | 
101 | Building on EC2 requires some IAM permissions setup:
102 | 
103 | 1. Create a new IAM _user_ with access keys and add [this custom inline policy](/aws/iam-serverless-chrome-automation-user-policy.json). The policy allows the minimum IAM permissions required to initiate a build on an EC2 Spot Instance.
104 | 1. Create a new IAM _role_ called "serverless-chrome-automation" with an EC2 trust entity and add [this custom inline policy](/aws/iam-serverless-chrome-automation-role-policy.json). The policy allows the minimum IAM permissions required to retrieve secrets from the Parameter Store, log the instance's stdout/stderr to CloudWatch, and upload binaries to S3. Be sure to update the Resource Arns where appropriate (e.g. for the S3 bucket). Make note of the `Instance Profile ARN` as you'll need to set the `AWS_IAM_INSTANCE_ARN` environment variable to it.
105 | 
106 | Next, export the following environment variables in your shell:
107 | 
108 | ```bash
109 | export AWS_ACCESS_KEY_ID=<your-iam-user-access-key-created-in-step-1>
110 | export AWS_SECRET_ACCESS_KEY=<your-iam-user-secret-created-in-step-1>
111 | export AWS_IAM_INSTANCE_ARN=<your-iam-role-instance-arn-created-in-step-2>
112 | export S3_BUCKET=<your-s3-bucket-and-optional-prefix>
113 | export FORCE_NEW_BUILD=1
114 | ```
115 | 
116 | Then to start a new build run the following, replacing the version and/or channel if desired:
117 | 
118 | ```bash
119 | ./scripts/ec2-build.sh chromium canary 64.0.3272.0
120 | ```
121 | 
122 | The version can also be ommitted. This will build the latest version based on the channel (one of `stable`, `beta`, or `dev`). Canary builds require an explicit version to be defined.
123 | 
124 | If successfull, the binary will show up in the S3 bucket. Check the CloudWatch `serverless-chrome-automation` log group [logs](https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logStream:group=/serverless-chrome-automation;streamFilter=typeLogStreamPrefix) for errors.
125 | 
126 | EC2 Spot Instance specifications such as instance type or spot price can be configured via [`/aws/ec2-spot-instance-specification.json`](/aws/ec2-spot-instance-specification.json).
127 | 
128 | ## Fonts
129 | 
130 | @TODO: document this.
131 | 
132 | 
133 | ## Known Issues / Limitations
134 | 
135 | 1. Hack to Chrome code to disable `/dev/shm`. Details [here](https://medium.com/@marco.luethy/running-headless-chrome-on-aws-lambda-fa82ad33a9eb).
136 | 1. [Hack](https://github.com/adieuadieu/serverless-chrome/issues/41#issuecomment-341712878) to disable Sandbox IPC Polling.
137 | 


--------------------------------------------------------------------------------
/docs/circleci.md:
--------------------------------------------------------------------------------
 1 | # CircleCI Setup
 2 | 
 3 | How to setup Circle CI for continuous integration/deployment (aka notes for project maintainer in case they forget).
 4 | 
 5 | Jobs and workflows defined in `~/.circleci/config.yml`
 6 | 
 7 | ## Build Settings
 8 | 
 9 | ### Environment Variables
10 | 
11 | For automated releases, environment requires the following variables:
12 | 
13 | - **AWS_IAM_INSTANCE_ARN** - The instance Arn for Spot instances launched when new binaries are to be built. Something like `arn:aws:iam::000000000000:instance-profile/serverless-chrome-automation`
14 | - **AWS_REGION** - Region in which to launch spot instances
15 | - **NPM_TOKEN** - NPM token for publishing packages. Use a bot account!
16 | - **GIT_USER_EMAIL** - email for commits made during Continuous Deployment processes.
17 | - **GIT_USER_NAME** - user's name for commits made during Continuous Deployment processes
18 | 
19 | Bla bla:
20 | 
21 | - CODACY_PROJECT_TOKEN
22 | - COVERALLS_REPO_TOKEN
23 | - COVERALLS_SERVICE_NAME
24 | 
25 | ### Advanced Settings
26 | 
27 | - **Pass secrets to builds from forked pull requests**: Off!
28 | 
29 | 
30 | ## Permissions
31 | 
32 | ### Checkout SSH Keys
33 | 
34 | Yes. Requires a user key for github.com so that nightly binary versions updates can be tagged/released when appropriate. Use a bot account!
35 | 
36 | 
37 | ### AWS Permissions
38 | 
39 | Yes. Needed.
40 | 
41 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/README.md:
--------------------------------------------------------------------------------
  1 | # Serverless-framework based Example Functions
  2 | 
  3 | A collection of [Serverless-framework](https://github.com/serverless/serverless) based functions for AWS Lambda demonstrating the `serverless-plugin-chrome` plugin for Serverless to run Headless Chrome serverless-ly. The example functions include:
  4 |   - A Print to PDF handler
  5 |   - A Capture Screenshot handler
  6 |   - A Page-load Request Logger handler
  7 |   - A Version Info handler (💤 )
  8 | 
  9 | 
 10 | ## Contents
 11 | 1. [Installation](#installation)
 12 | 1. [Credentials](#credentials)
 13 | 1. [Deployment](#deployment)
 14 | 1. [Example Functions](#example-functions)
 15 | 1. [Local Development](#local-development)
 16 | 1. [Configuration](#configuration)
 17 | 
 18 | 
 19 | ## Installation
 20 | 
 21 | First, install `serverless` globally:
 22 | 
 23 | ```bash
 24 | npm install serverless -g
 25 | ```
 26 | 
 27 | Then pull down the example service:
 28 | 
 29 | ```bash
 30 | serverless create -u \
 31 |   https://github.com/adieuadieu/serverless-chrome/tree/master/examples/serverless-framework/aws
 32 | ```
 33 | 
 34 | And install the dependencies:
 35 | 
 36 | ```bash
 37 | npm install
 38 | ```
 39 | 
 40 | ## Credentials
 41 | 
 42 | _We recommend using a tool like [AWS Vault](https://github.com/99designs/aws-vault) to manage your AWS credentials._
 43 | 
 44 | You must configure your AWS credentials either by defining `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environmental variables, or using an AWS profile. You can read more about this on the [Serverless Credentials Guide](https://serverless.com/framework/docs/providers/aws/guide/credentials/).
 45 | 
 46 | In short, either:
 47 | 
 48 | ```bash
 49 | export AWS_PROFILE=<your-profile-name>
 50 | ```
 51 | 
 52 | or
 53 | 
 54 | ```bash
 55 | export AWS_ACCESS_KEY_ID=<your-key-here>
 56 | export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>
 57 | ```
 58 | 
 59 | ## Deployment
 60 | 
 61 | Once Credentials are set up, to deploy the full service run:
 62 | 
 63 | ```bash
 64 | npm run deploy
 65 | ```
 66 | 
 67 | ## Example Functions
 68 | 
 69 | This example service includes the following functions, each demonstrating a common pattern/use-case.
 70 | 
 71 | 
 72 | ### Capture Screenshot of a given URL
 73 |  When you the serverless function, it creates a Lambda function which will take a screenshot of a URL it's provided. You can provide this URL to the Lambda function via the AWS API Gateway. After a successful deploy, an API endpoint will be provided. Use this URL to call the Lambda function with a url in the query string. E.g. `https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/screenshot?url=https://github.com/adieuadieu/serverless-chrome`. Add `&mobile=1` for mobile device view.
 74 | 
 75 | #### Deploying
 76 | 
 77 | To deploy the Capture Screenshot function:
 78 | 
 79 | ```bash
 80 | serverless deploy -f screenshot
 81 | ```
 82 | 
 83 | ### Print a given URL to PDF
 84 | The printToPdf handler will create a PDF from a URL it's provided. You can provide this URL to the Lambda function via the AWS API Gateway. After a successful deploy, an API endpoint will be provided. Use this URL to call the Lambda function with a url in the query string. E.g. `https://XXXXXXX.execute-api.us-weeast-2.amazonaws.com/dev/pdf?url=https://github.com/adieuadieu/serverless-chrome`
 85 | 
 86 | This handler also supports configuring the "paper" size, orientation, etc. You can pass any of the DevTools Protocol's [`Page.printToPdf()`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF]) method's parameters. For example, for landscape oriented PDF add `&landscape=true` to the end of the URL. Be sure to remember to escape the value of `url` if it contains query parameters. E.g. `https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/pdf?url=https://github.com/adieuadieu/serverless-chrome&landscape=true`
 87 | 
 88 | #### Deploying
 89 | 
 90 | To deploy the Capture Screenshot function:
 91 | 
 92 | ```bash
 93 | serverless deploy -f pdf
 94 | ```
 95 | 
 96 | 
 97 | ### Page-load Request Logger
 98 | Returns an array of every request which was made loading a given page.
 99 | 
100 | #### Deploying
101 | 
102 | To deploy the Page-load Request Logger function:
103 | 
104 | ```bash
105 | serverless deploy -f request-logger
106 | ```
107 | 
108 | 
109 | ### Chrome Version Info
110 |  Prints version info of headless chrome binary
111 | 
112 | #### Deploying
113 | 
114 | To deploy the Chrome Version Info function:
115 | 
116 | ```bash
117 | serverless deploy -f version-info
118 | ```
119 | 
120 | ## Configuration
121 | 
122 | These are simple functions and don't offer any configuration options. Take a look at the `serverless-plugins-chrome` plugin's [README](/packages/serverless-plugin) for it's configuration options.
123 | 
124 | 
125 | ## Local Development
126 | 
127 | Go for it. Locally, if installed, Chrome will be launched. More in the plugin's [README](/packages/serverless-plugin).
128 | 
129 | Invoke a function locally with:
130 | 
131 | ```bash
132 | serverless invoke local -f replaceThisWithTheFunctionName
133 | ```
134 | 
135 | 
136 | ## Troubleshooting
137 | 
138 | <details id="ts-aws-client-timeout">
139 |   <summary>I keep getting a timeout error when deploying and it's really annoying.</summary>
140 | 
141 |   Indeed, that is annoying. I've had the same problem, and so that's why it's now here in this troubleshooting section. This may be an issue in the underlying AWS SDK when using a slower Internet connection. Try changing the `AWS_CLIENT_TIMEOUT` environment variable to a higher value. For example, in your command prompt enter the following and try deploying again:
142 | 
143 | ```bash
144 | export AWS_CLIENT_TIMEOUT=3000000
145 | ```
146 | </details>
147 | 
148 | <details id="ts-argh">
149 |   <summary>Aaaaaarggghhhhhh!!!</summary>
150 | 
151 |   Uuurrrggghhhhhh! Have you tried [filing an Issue](https://github.com/adieuadieu/serverless-chrome/issues/new)?
152 | </details>
153 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@serverless-chrome/example-serverless-framework-aws",
 3 |   "private": true,
 4 |   "version": "1.0.0-70",
 5 |   "description": "Example serverless functions using the Serverless-framework",
 6 |   "main": "src/handlers.js",
 7 |   "engines": {
 8 |     "node": ">= 6.10.0"
 9 |   },
10 |   "config": {
11 |     "jsSrc": "src/",
12 |     "chromiumChannel": "dev",
13 |     "chromium_channel": "dev"
14 |   },
15 |   "scripts": {
16 |     "test": "npm run lint && npm run ava",
17 |     "watch:test": "ava --watch",
18 |     "ava": "ava",
19 |     "lint": "npm run lint:eslint -s",
20 |     "lint:eslint": "eslint $npm_package_config_jsSrc",
21 |     "deploy": "serverless deploy -v",
22 |     "upgrade-dependencies": "yarn upgrade-interactive --latest --exact"
23 |   },
24 |   "repository": {
25 |     "type": "git",
26 |     "url": "https://github.com/adieuadieu/serverless-chrome.git"
27 |   },
28 |   "keywords": [
29 |     "serverless",
30 |     "chrome",
31 |     "chromium",
32 |     "headless",
33 |     "aws",
34 |     "lambda",
35 |     "serverless-framework",
36 |     "screenshot",
37 |     "screen capture",
38 |     "pdf"
39 |   ],
40 |   "author": "Marco Lüthy",
41 |   "license": "MIT",
42 |   "bugs": {
43 |     "url": "https://github.com/adieuadieu/serverless-chrome/issues"
44 |   },
45 |   "homepage": "https://github.com/adieuadieu/serverless-chrome/tree/master/examples/serverless-framework/aws",
46 |   "dependencies": {
47 |     "chrome-remote-interface": "0.25.3"
48 |   },
49 |   "devDependencies": {
50 |     "ava": "0.23.0",
51 |     "babel-core": "6.26.0",
52 |     "babel-loader": "7.1.2",
53 |     "babel-plugin-transform-object-entries": "1.0.0",
54 |     "babel-plugin-transform-object-rest-spread": "6.26.0",
55 |     "babel-preset-env": "1.6.1",
56 |     "babel-preset-stage-3": "6.24.1",
57 |     "babel-register": "6.26.0",
58 |     "serverless": "1.24.1",
59 |     "serverless-plugin-chrome": "1.0.0-70",
60 |     "serverless-webpack": "4.0.0",
61 |     "webpack": "3.8.1"
62 |   },
63 |   "ava": {
64 |     "require": "babel-register",
65 |     "babel": "inherits"
66 |   },
67 |   "babel": {
68 |     "sourceMaps": true,
69 |     "presets": [
70 |       [
71 |         "env",
72 |         {
73 |           "modules": "commonjs",
74 |           "targets": {
75 |             "node": "6.10"
76 |           },
77 |           "include": [
78 |             "es7.object.values",
79 |             "es7.object.entries"
80 |           ]
81 |         }
82 |       ],
83 |       "stage-3"
84 |     ],
85 |     "plugins": [
86 |       "transform-object-rest-spread",
87 |       "transform-object-entries"
88 |     ]
89 |   }
90 | }
91 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/serverless.yml:
--------------------------------------------------------------------------------
 1 | service: serverless-chrome-examples
 2 | 
 3 | provider:
 4 |   name: aws
 5 |   runtime: nodejs12.x
 6 |   stage: dev
 7 |   region: us-east-1
 8 |   environment:
 9 |     PAGE_LOAD_TIMEOUT: 20000
10 |     LOGGING: true
11 | 
12 | plugins:
13 |   - serverless-plugin-chrome
14 |   - serverless-webpack
15 | 
16 | custom:
17 |   chrome:
18 |     flags:
19 |       - --window-size=1280,1696 # Letter size
20 |       - --hide-scrollbars
21 | 
22 | functions:
23 |   version-info:
24 |     description: Headless Chrome Serverless-framework version info example
25 |     memorySize: 1024
26 |     timeout: 30
27 |     handler: src/handlers/version.default
28 |     events:
29 |       - http:
30 |           path: version-info
31 |           method: get
32 | 
33 |   request-logger:
34 |     description: Headless Chrome Serverless-framework request logging example
35 |     memorySize: 1024
36 |     timeout: 30
37 |     handler: src/handlers/requestLogger.default
38 |     events:
39 |       - http:
40 |           path: request-logger
41 |           method: get
42 | 
43 |   screenshot:
44 |     description: Headless Chrome Serverless-framework screenshot example
45 |     memorySize: 1536
46 |     timeout: 30
47 |     handler: src/handlers/screenshot.default
48 |     events:
49 |       - http:
50 |           path: screenshot
51 |           method: get
52 | 
53 |   pdf:
54 |     description: Headless Chrome Serverless-framework PDF example
55 |     memorySize: 1536
56 |     timeout: 30
57 |     handler: src/handlers/pdf.default
58 |     events:
59 |       - http:
60 |           path: pdf
61 |           method: get
62 | 
63 | resources:
64 |   Resources:
65 |     ApiGatewayRestApi:
66 |       Properties:
67 |         BinaryMediaTypes:
68 |           - "*/*"
69 | 
70 |     # Enable X-Ray tracing on Lambda functions
71 |     # ScreenshotLambdaFunction:
72 |     #   Properties:
73 |     #     TracingConfig:
74 |     #       Mode: Active
75 |     # PdfLambdaFunction:
76 |     #   Properties:
77 |     #     TracingConfig:
78 |     #       Mode: Active
79 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/chrome/pdf.js:
--------------------------------------------------------------------------------
  1 | //
  2 | //
  3 | // HEY! Be sure to re-incorporate changes from @albinekb
  4 | // https://github.com/adieuadieu/serverless-chrome/commit/fca8328134f1098adf92e115f69002e69df24238
  5 | //
  6 | //
  7 | //
  8 | import Cdp from 'chrome-remote-interface'
  9 | import log from '../utils/log'
 10 | import sleep from '../utils/sleep'
 11 | 
 12 | const defaultPrintOptions = {
 13 |   landscape: false,
 14 |   displayHeaderFooter: false,
 15 |   printBackground: true,
 16 |   scale: 1,
 17 |   paperWidth: 8.27, // aka A4
 18 |   paperHeight: 11.69, // aka A4
 19 |   marginTop: 0,
 20 |   marginBottom: 0,
 21 |   marginLeft: 0,
 22 |   marginRight: 0,
 23 |   pageRanges: '',
 24 | }
 25 | 
 26 | function cleanPrintOptionValue (type, value) {
 27 |   const types = { string: String, number: Number, boolean: Boolean }
 28 |   return types[type](value)
 29 | }
 30 | 
 31 | export function makePrintOptions (options = {}) {
 32 |   return Object.entries(options).reduce(
 33 |     (printOptions, [option, value]) => ({
 34 |       ...printOptions,
 35 |       [option]: cleanPrintOptionValue(
 36 |         typeof defaultPrintOptions[option],
 37 |         value
 38 |       ),
 39 |     }),
 40 |     defaultPrintOptions
 41 |   )
 42 | }
 43 | 
 44 | export default async function printUrlToPdf (
 45 |   url,
 46 |   printOptions = {},
 47 |   mobile = false
 48 | ) {
 49 |   const LOAD_TIMEOUT = process.env.PAGE_LOAD_TIMEOUT || 1000 * 20
 50 |   let result
 51 | 
 52 |   // @TODO: write a better queue, which waits a few seconds when reaching 0
 53 |   // before emitting "empty". Also see other handlers.
 54 |   const requestQueue = []
 55 | 
 56 |   const emptyQueue = async () => {
 57 |     await sleep(1000)
 58 | 
 59 |     log('Request queue size:', requestQueue.length, requestQueue)
 60 | 
 61 |     if (requestQueue.length > 0) {
 62 |       await emptyQueue()
 63 |     }
 64 |   }
 65 | 
 66 |   const tab = await Cdp.New()
 67 |   const client = await Cdp({ host: '127.0.0.1', target: tab })
 68 | 
 69 |   const {
 70 |     Network, Page, Runtime, Emulation,
 71 |   } = client
 72 | 
 73 |   Network.requestWillBeSent((data) => {
 74 |     // only add requestIds which aren't already in the queue
 75 |     // why? if a request to http gets redirected to https, requestId remains the same
 76 |     if (!requestQueue.find(item => item === data.requestId)) {
 77 |       requestQueue.push(data.requestId)
 78 |     }
 79 | 
 80 |     log('Chrome is sending request for:', data.requestId, data.request.url)
 81 |   })
 82 | 
 83 |   Network.responseReceived(async (data) => {
 84 |     // @TODO: handle this better. sometimes images, fonts,
 85 |     // etc aren't done loading before we think loading is finished
 86 |     // is there a better way to detect this? see if there's any pending
 87 |     // js being executed? paints? something?
 88 |     await sleep(100) // wait here, in case this resource has triggered more resources to load.
 89 |     requestQueue.splice(
 90 |       requestQueue.findIndex(item => item === data.requestId),
 91 |       1
 92 |     )
 93 |     log('Chrome received response for:', data.requestId, data.response.url)
 94 |   })
 95 | 
 96 |   try {
 97 |     await Promise.all([Network.enable(), Page.enable()])
 98 | 
 99 |     await Page.navigate({ url })
100 | 
101 |     await Page.loadEventFired()
102 | 
103 |     const {
104 |       result: {
105 |         value: { height },
106 |       },
107 |     } = await Runtime.evaluate({
108 |       expression: `(
109 |         () => {
110 |           const height = document.body.scrollHeight
111 |           window.scrollTo(0, height)
112 |           return { height }
113 |         }
114 |       )();
115 |       `,
116 |       returnByValue: true,
117 |     })
118 | 
119 |     // setting the viewport to the size of the page will force
120 |     // any lazy-loaded images to load
121 |     await Emulation.setDeviceMetricsOverride({
122 |       mobile: !!mobile,
123 |       deviceScaleFactor: 0,
124 |       scale: 1, // mobile ? 2 : 1,
125 |       width: mobile ? 375 : 1280,
126 |       height,
127 |     })
128 | 
129 |     await new Promise((resolve, reject) => {
130 |       const timeout = setTimeout(
131 |         reject,
132 |         LOAD_TIMEOUT,
133 |         new Error(`Page load timed out after ${LOAD_TIMEOUT} ms.`)
134 |       )
135 | 
136 |       const load = async () => {
137 |         await emptyQueue()
138 |         clearTimeout(timeout)
139 |         resolve()
140 |       }
141 | 
142 |       load()
143 |     })
144 | 
145 |     log('We think the page has finished loading. Printing PDF.')
146 | 
147 |     const pdf = await Page.printToPDF(printOptions)
148 |     result = pdf.data
149 |   } catch (error) {
150 |     console.error(error)
151 |   }
152 | 
153 |   await client.close()
154 | 
155 |   return result
156 | }
157 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/chrome/pdf.test.js:
--------------------------------------------------------------------------------
 1 | import test from 'ava'
 2 | import pdf from './pdf'
 3 | 
 4 | const testUrl = 'https://github.com/adieuadieu'
 5 | 
 6 | test('printUrlToPdf() should return base64 encoded application/pdf', async (t) => {
 7 |   await t.notThrows(async () => {
 8 |     const result = await pdf(testUrl)
 9 | 
10 |     t.is(typeof result, 'string')
11 |   })
12 | })
13 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/chrome/screenshot.js:
--------------------------------------------------------------------------------
 1 | import Cdp from 'chrome-remote-interface'
 2 | import log from '../utils/log'
 3 | import sleep from '../utils/sleep'
 4 | 
 5 | export default async function captureScreenshotOfUrl (url, mobile = false) {
 6 |   const LOAD_TIMEOUT = process.env.PAGE_LOAD_TIMEOUT || 1000 * 60
 7 | 
 8 |   let result
 9 |   let loaded = false
10 | 
11 |   const loading = async (startTime = Date.now()) => {
12 |     if (!loaded && Date.now() - startTime < LOAD_TIMEOUT) {
13 |       await sleep(100)
14 |       await loading(startTime)
15 |     }
16 |   }
17 | 
18 |   const [tab] = await Cdp.List()
19 |   const client = await Cdp({ host: '127.0.0.1', target: tab })
20 | 
21 |   const {
22 |     Network, Page, Runtime, Emulation,
23 |   } = client
24 | 
25 |   Network.requestWillBeSent((params) => {
26 |     log('Chrome is sending request for:', params.request.url)
27 |   })
28 | 
29 |   Page.loadEventFired(() => {
30 |     loaded = true
31 |   })
32 | 
33 |   try {
34 |     await Promise.all([Network.enable(), Page.enable()])
35 | 
36 |     if (mobile) {
37 |       await Network.setUserAgentOverride({
38 |         userAgent:
39 |           'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1',
40 |       })
41 |     }
42 | 
43 |     await Emulation.setDeviceMetricsOverride({
44 |       mobile: !!mobile,
45 |       deviceScaleFactor: 0,
46 |       scale: 1, // mobile ? 2 : 1,
47 |       width: mobile ? 375 : 1280,
48 |       height: 0,
49 |     })
50 | 
51 |     await Page.navigate({ url })
52 |     await Page.loadEventFired()
53 |     await loading()
54 | 
55 |     const {
56 |       result: {
57 |         value: { height },
58 |       },
59 |     } = await Runtime.evaluate({
60 |       expression: `(
61 |         () => ({ height: document.body.scrollHeight })
62 |       )();
63 |       `,
64 |       returnByValue: true,
65 |     })
66 | 
67 |     await Emulation.setDeviceMetricsOverride({
68 |       mobile: !!mobile,
69 |       deviceScaleFactor: 0,
70 |       scale: 1, // mobile ? 2 : 1,
71 |       width: mobile ? 375 : 1280,
72 |       height,
73 |     })
74 | 
75 |     const screenshot = await Page.captureScreenshot({ format: 'png' })
76 | 
77 |     result = screenshot.data
78 |   } catch (error) {
79 |     console.error(error)
80 |   }
81 | 
82 |   await client.close()
83 | 
84 |   return result
85 | }
86 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/chrome/screenshot.test.js:
--------------------------------------------------------------------------------
 1 | import test from 'ava'
 2 | import screenshot from './screenshot'
 3 | 
 4 | const testUrl = 'https://github.com/adieuadieu'
 5 | 
 6 | test('screenshot() should return base64 encoded image/png', async (t) => {
 7 |   await t.notThrows(async () => {
 8 |     const result = await screenshot(testUrl)
 9 | 
10 |     t.is(typeof result, 'string')
11 |   })
12 | })
13 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/chrome/version.js:
--------------------------------------------------------------------------------
1 | import Cdp from 'chrome-remote-interface'
2 | 
3 | export default async function version () {
4 |   return Cdp.Version()
5 | }
6 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/handlers/pdf.js:
--------------------------------------------------------------------------------
 1 | //
 2 | //
 3 | // HEY! Be sure to re-incorporate changes from @albinekb
 4 | // https://github.com/adieuadieu/serverless-chrome/commit/fca8328134f1098adf92e115f69002e69df24238
 5 | //
 6 | //
 7 | //
 8 | import log from '../utils/log'
 9 | import pdf, { makePrintOptions } from '../chrome/pdf'
10 | 
11 | export default async function handler (event, context, callback) {
12 |   const queryStringParameters = event.queryStringParameters || {}
13 |   const {
14 |     url = 'https://github.com/adieuadieu/serverless-chrome',
15 |     ...printParameters
16 |   } = queryStringParameters
17 |   const printOptions = makePrintOptions(printParameters)
18 |   let data
19 | 
20 |   log('Processing PDFification for', url, printOptions)
21 | 
22 |   const startTime = Date.now()
23 | 
24 |   try {
25 |     data = await pdf(url, printOptions)
26 |   } catch (error) {
27 |     console.error('Error printing pdf for', url, error)
28 |     return callback(error)
29 |   }
30 | 
31 |   log(`Chromium took ${Date.now() - startTime}ms to load URL and render PDF.`)
32 | 
33 |   // TODO: handle cases where the response is > 10MB
34 |   // with saving to S3 or something since API Gateway has a body limit of 10MB
35 |   return callback(null, {
36 |     statusCode: 200,
37 |     body: data,
38 |     isBase64Encoded: true,
39 |     headers: {
40 |       'Content-Type': 'application/pdf',
41 |     },
42 |   })
43 | }
44 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/handlers/pdf.test.js:
--------------------------------------------------------------------------------
 1 | import test from 'ava'
 2 | import handler from '../handlers/pdf'
 3 | 
 4 | const testUrl = 'https://github.com/adieuadieu'
 5 | const testEvent = {
 6 |   queryStringParameters: { url: testUrl },
 7 | }
 8 | 
 9 | test('PDF handler', async (t) => {
10 |   await t.notThrows(async () => {
11 |     const result = await handler(testEvent, {})
12 | 
13 |     t.is(result.statusCode, 200)
14 |   })
15 | })
16 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/handlers/requestLogger.js:
--------------------------------------------------------------------------------
 1 | /* eslint-disable import/prefer-default-export */
 2 | import Cdp from 'chrome-remote-interface'
 3 | 
 4 | const LOAD_TIMEOUT = 1000 * 30
 5 | 
 6 | export default async function handler (event, context, callback) {
 7 |   const queryStringParameters = event.queryStringParameters || {}
 8 |   const {
 9 |     url = 'https://github.com/adieuadieu/serverless-chrome',
10 |   } = queryStringParameters
11 |   const requestsMade = []
12 | 
13 |   const [tab] = await Cdp.List()
14 |   const client = await Cdp({ host: '127.0.0.1', target: tab })
15 | 
16 |   const { Network, Page } = client
17 | 
18 |   Network.requestWillBeSent(params => requestsMade.push(params))
19 | 
20 |   const loadEventFired = Page.loadEventFired()
21 | 
22 |   // https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-enable
23 |   await Network.enable()
24 | 
25 |   // https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-enable
26 |   await Page.enable()
27 | 
28 |   // https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-navigate
29 |   await Page.navigate({ url })
30 | 
31 |   // wait until page is done loading, or timeout
32 |   await new Promise((resolve, reject) => {
33 |     const timeout = setTimeout(
34 |       reject,
35 |       LOAD_TIMEOUT,
36 |       new Error(`Page load timed out after ${LOAD_TIMEOUT} ms.`)
37 |     )
38 | 
39 |     loadEventFired.then(async () => {
40 |       clearTimeout(timeout)
41 |       resolve()
42 |     })
43 |   })
44 |   // It's important that we close the websocket connection,
45 |   // or our Lambda function will not exit properly
46 |   await client.close()
47 | 
48 |   callback(null, {
49 |     statusCode: 200,
50 |     body: JSON.stringify({
51 |       requestsMade,
52 |     }),
53 |     headers: {
54 |       'Content-Type': 'application/json',
55 |     },
56 |   })
57 | }
58 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/handlers/screencast.js:
--------------------------------------------------------------------------------
  1 | // EXPERIMENTAL
  2 | 
  3 | /*
  4 | @todo: time the duration between screenshot frames, and create ffmpeg video
  5 |   based on duration between framesCaptured
  6 |   see: https://github.com/peterc/chrome2gif/blob/master/index.js#L34
  7 | */
  8 | 
  9 | import fs from 'fs'
 10 | import path from 'path'
 11 | import { spawn, execSync } from 'child_process'
 12 | import Cdp from 'chrome-remote-interface'
 13 | import log from '../utils/log'
 14 | import sleep from '../utils/sleep'
 15 | 
 16 | const defaultOptions = {
 17 |   captureFrameRate: 1,
 18 |   captureQuality: 50,
 19 |   videoFrameRate: '5',
 20 |   videoFrameSize: '848x640',
 21 | }
 22 | 
 23 | const FFMPEG_PATH = path.resolve('./ffmpeg')
 24 | 
 25 | function cleanPrintOptionValue (type, value) {
 26 |   const types = { string: String, number: Number, boolean: Boolean }
 27 |   return value ? new types[type](value) : undefined
 28 | }
 29 | 
 30 | function makePrintOptions (options = {}) {
 31 |   return Object.entries(options).reduce(
 32 |     (printOptions, [option, value]) => ({
 33 |       ...printOptions,
 34 |       [option]: cleanPrintOptionValue(typeof defaultOptions[option], value),
 35 |     }),
 36 |     defaultOptions
 37 |   )
 38 | }
 39 | 
 40 | export async function makeVideo (url, options = {}, invokeid = '') {
 41 |   const LOAD_TIMEOUT = process.env.PAGE_LOAD_TIMEOUT || 1000 * 20
 42 |   let result
 43 |   let loaded = false
 44 |   let framesCaptured = 0
 45 | 
 46 |   // @TODO: write a better queue, which waits a few seconds when reaching 0
 47 |   // before emitting "empty"
 48 |   const requestQueue = []
 49 | 
 50 |   const loading = async (startTime = Date.now()) => {
 51 |     log('Request queue size:', requestQueue.length, requestQueue)
 52 | 
 53 |     if ((!loaded || requestQueue.length > 0) && Date.now() - startTime < LOAD_TIMEOUT) {
 54 |       await sleep(100)
 55 |       await loading(startTime)
 56 |     }
 57 |   }
 58 | 
 59 |   const tab = await Cdp.New()
 60 |   const client = await Cdp({ host: '127.0.0.1', target: tab })
 61 | 
 62 |   const {
 63 |     Network, Page, Input, DOM,
 64 |   } = client
 65 | 
 66 |   Network.requestWillBeSent((data) => {
 67 |     // only add requestIds which aren't already in the queue
 68 |     // why? if a request to http gets redirected to https, requestId remains the same
 69 |     if (!requestQueue.find(item => item === data.requestId)) {
 70 |       requestQueue.push(data.requestId)
 71 |     }
 72 | 
 73 |     log('Chrome is sending request for:', data.requestId, data.request.url)
 74 |   })
 75 | 
 76 |   Network.responseReceived(async (data) => {
 77 |     // @TODO: handle this better. sometimes images, fonts, etc aren't done
 78 |     // loading before we think loading is finished
 79 |     // is there a better way to detect this? see if there's any pending js
 80 |     // being executed? paints? something?
 81 |     await sleep(100) // wait here, in case this resource has triggered more resources to load.
 82 |     requestQueue.splice(requestQueue.findIndex(item => item === data.requestId), 1)
 83 |     log('Chrome received response for:', data.requestId, data.response.url)
 84 |   })
 85 | 
 86 |   // @TODO: check for/catch error/failures to load a resource
 87 |   // Network.loadingFailed
 88 |   // Network.loadingFinished
 89 |   // @TODO: check for results from cache, which don't trigger responseReceived
 90 |   // (Network.requestServedFromCache instead)
 91 |   // - if the request is cached you will get a "requestServedFromCache" event instead
 92 |   // of "responseReceived" (and no "loadingFinished" event)
 93 |   Page.loadEventFired((data) => {
 94 |     loaded = true
 95 |     log('Page.loadEventFired', data)
 96 |   })
 97 | 
 98 |   Page.domContentEventFired((data) => {
 99 |     log('Page.domContentEventFired', data)
100 |   })
101 | 
102 |   Page.screencastFrame(({ sessionId, data, metadata }) => {
103 |     const filename = `/tmp/frame-${invokeid}-${String(metadata.timestamp).replace('.', '')}.jpg`
104 |     framesCaptured += 1
105 | 
106 |     // log('Received screencast frame', sessionId, metadata)
107 |     Page.screencastFrameAck({ sessionId })
108 | 
109 |     fs.writeFile(filename, data, { encoding: 'base64' }, (error) => {
110 |       log('Page.screencastFrame writeFile:', filename, error)
111 |     })
112 |   })
113 | 
114 |   if (process.env.LOGGING === 'TRUE') {
115 |     Cdp.Version((err, info) => {
116 |       console.log('CDP version info', err, info)
117 |     })
118 |   }
119 | 
120 |   try {
121 |     await Promise.all([Network.enable(), Page.enable(), DOM.enable()])
122 | 
123 |     const interactionStartTime = Date.now()
124 | 
125 |     await client.send('Overlay.enable') // this has to happen after DOM.enable()
126 |     await client.send('Overlay.setShowFPSCounter', { show: true })
127 | 
128 |     await Page.startScreencast({
129 |       format: 'jpeg',
130 |       quality: options.captureQuality,
131 |       everyNthFrame: options.captureFrameRate,
132 |     })
133 | 
134 |     await Page.navigate({ url })
135 |     await loading()
136 |     await sleep(2000)
137 |     await Input.synthesizeScrollGesture({ x: 50, y: 50, yDistance: -2000 })
138 |     await sleep(1000)
139 | 
140 |     await Page.stopScreencast()
141 | 
142 |     log('We think the page has finished doing what it do. Rendering video.')
143 |     log(`Interaction took ${Date.now() - interactionStartTime}ms to finish.`)
144 |   } catch (error) {
145 |     console.error(error)
146 |   }
147 | 
148 |   // @TODO: handle this better —
149 |   // If you don't close the tab, an a subsequent Page.navigate() is unable to load the url,
150 |   // you'll end up printing a PDF of whatever was loaded in the tab previously
151 |   // (e.g. a previous URL) _unless_ you Cdp.New() each time. But still good to close to
152 |   // clear up memory in Chrome
153 |   try {
154 |     log('trying to close tab', tab)
155 |     await Cdp.Close({ id: tab.id })
156 |   } catch (error) {
157 |     log('unable to close tab', tab, error)
158 |   }
159 | 
160 |   await client.close()
161 | 
162 |   const renderVideo = async () => {
163 |     await new Promise((resolve, reject) => {
164 |       const args = [
165 |         '-y',
166 |         '-loglevel',
167 |         'warning', // 'debug',
168 |         '-f',
169 |         'image2',
170 |         '-framerate',
171 |         `${options.videoFrameRate}`,
172 |         '-pattern_type',
173 |         'glob',
174 |         '-i',
175 |         `"/tmp/frame-${invokeid}-*.jpg"`,
176 |         // '-r',
177 |         '-s',
178 |         `${options.videoFrameSize}`,
179 |         '-c:v',
180 |         'libx264',
181 |         '-pix_fmt',
182 |         'yuv420p',
183 |         '/tmp/video.mp4',
184 |       ]
185 | 
186 |       log('spawning ffmpeg with args', FFMPEG_PATH, args.join(' '))
187 | 
188 |       const ffmpeg = spawn(FFMPEG_PATH, args, { cwd: '/tmp', shell: true })
189 |       ffmpeg.on('message', msg => log('ffmpeg message', msg))
190 |       ffmpeg.on('error', msg => log('ffmpeg error', msg) && reject(msg))
191 |       ffmpeg.on('close', (status) => {
192 |         if (status !== 0) {
193 |           log('ffmpeg closed with status', status)
194 |           return reject(new Error(`ffmpeg closed with status ${status}`))
195 |         }
196 | 
197 |         return resolve()
198 |       })
199 | 
200 |       ffmpeg.stdout.on('data', (data) => {
201 |         log(`ffmpeg stdout: ${data}`)
202 |       })
203 | 
204 |       ffmpeg.stderr.on('data', (data) => {
205 |         log(`ffmpeg stderr: ${data}`)
206 |       })
207 |     })
208 | 
209 |     // @TODO: no sync-y syncface sync
210 |     return fs.readFileSync('/tmp/video.mp4', { encoding: 'base64' })
211 |   }
212 | 
213 |   try {
214 |     const renderStartTime = Date.now()
215 |     result = await renderVideo()
216 |     log(`FFmpeg took ${Date.now() - renderStartTime}ms to finish.`)
217 |   } catch (error) {
218 |     console.error('Error making video', error)
219 |   }
220 | 
221 |   // @TODO: this clean up .. do it better. and not sync
222 |   // clean up old frames
223 |   console.log('rm', execSync('rm -Rf /tmp/frame-*').toString())
224 |   console.log('rm', execSync('rm -Rf /tmp/video*').toString())
225 | 
226 |   return { data: result, framesCaptured }
227 | }
228 | 
229 | export default async function handler (event, { invokeid }, callback) {
230 |   const queryStringParameters = event.queryStringParameters || {}
231 |   const { url, ...printParameters } = queryStringParameters
232 |   const options = makePrintOptions(printParameters)
233 |   let result = {}
234 | 
235 |   log('Processing PDFification for', url, options)
236 | 
237 |   const startTime = Date.now()
238 | 
239 |   try {
240 |     result = await makeVideo(url, options, invokeid)
241 |   } catch (error) {
242 |     console.error('Error printing pdf for', url, error)
243 |     return callback(error)
244 |   }
245 | 
246 |   // TODO: probably better to write the pdf to S3,
247 |   // but that's a bit more complicated for this example.
248 |   return callback(null, {
249 |     statusCode: 200,
250 |     // it's not possible to send binary via AWS API Gateway as it expects JSON response from Lambda
251 |     body: `
252 |       <html>
253 |         <body>
254 |           <p><a href="${url}">${url}</a></p>
255 |           <p><code>${JSON.stringify(options, null, 2)}</code></p>
256 |           <p>It took Chromium & FFmpeg ${Date.now() -
257 |             startTime}ms to load URL, interact with the age and render video. Captured ${result.framesCaptured} frames.</p>
258 |           <embed src="data:video/mp4;base64,${result.data}" width="100%" height="80%" type='video/mp4'>
259 |         </body>
260 |       </html>
261 |     `,
262 |     headers: {
263 |       'Content-Type': 'text/html',
264 |     },
265 |   })
266 | }
267 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/handlers/screenshot.js:
--------------------------------------------------------------------------------
 1 | import log from '../utils/log'
 2 | import screenshot from '../chrome/screenshot'
 3 | 
 4 | export default async function handler (event, context, callback) {
 5 |   const queryStringParameters = event.queryStringParameters || {}
 6 |   const {
 7 |     url = 'https://github.com/adieuadieu/serverless-chrome',
 8 |     mobile = false,
 9 |   } = queryStringParameters
10 | 
11 |   let data
12 | 
13 |   log('Processing screenshot capture for', url)
14 | 
15 |   const startTime = Date.now()
16 | 
17 |   try {
18 |     data = await screenshot(url, mobile)
19 |   } catch (error) {
20 |     console.error('Error capturing screenshot for', url, error)
21 |     return callback(error)
22 |   }
23 | 
24 |   log(`Chromium took ${Date.now() - startTime}ms to load URL and capture screenshot.`)
25 | 
26 |   return callback(null, {
27 |     statusCode: 200,
28 |     body: data,
29 |     isBase64Encoded: true,
30 |     headers: {
31 |       'Content-Type': 'image/png',
32 |     },
33 |   })
34 | }
35 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/handlers/screenshot.test.js:
--------------------------------------------------------------------------------
 1 | import test from 'ava'
 2 | import handler from './screenshot'
 3 | 
 4 | const testUrl = 'https://github.com/adieuadieu'
 5 | const testEvent = {
 6 |   queryStringParameters: { url: testUrl },
 7 | }
 8 | 
 9 | test('Screenshot handler', async (t) => {
10 |   await t.notThrows(async () => {
11 |     const result = await handler(testEvent, {})
12 | 
13 |     t.is(result.statusCode, 200)
14 |   })
15 | })
16 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/handlers/version.js:
--------------------------------------------------------------------------------
 1 | import log from '../utils/log'
 2 | import version from '../chrome/version'
 3 | 
 4 | export default async function handler (event, context, callback) {
 5 |   let responseBody
 6 | 
 7 |   log('Getting version info.')
 8 | 
 9 |   try {
10 |     responseBody = await version()
11 |   } catch (error) {
12 |     console.error('Error getting version info')
13 |     return callback(error)
14 |   }
15 | 
16 |   return callback(null, {
17 |     statusCode: 200,
18 |     body: JSON.stringify(responseBody),
19 |     headers: {
20 |       'Content-Type': 'application/json',
21 |     },
22 |   })
23 | }
24 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/utils/log.js:
--------------------------------------------------------------------------------
1 | export default function log (...stuffToLog) {
2 |   if (process.env.LOGGING) {
3 |     console.log(...stuffToLog)
4 |   }
5 | }
6 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/src/utils/sleep.js:
--------------------------------------------------------------------------------
1 | export default function sleep (miliseconds = 100) {
2 |   return new Promise(resolve => setTimeout(() => resolve(), miliseconds))
3 | }
4 | 


--------------------------------------------------------------------------------
/examples/serverless-framework/aws/webpack.config.js:
--------------------------------------------------------------------------------
 1 | const webpack = require('webpack')
 2 | const slsw = require('serverless-webpack')
 3 | 
 4 | module.exports = {
 5 |   devtool: 'source-map',
 6 |   target: 'node',
 7 |   node: {
 8 |     __dirname: true,
 9 |   },
10 |   module: {
11 |     rules: [
12 |       {
13 |         test: /\.jsx?$/,
14 |         loader: 'babel-loader',
15 |         exclude: /node_modules/,
16 |         options: {
17 |           cacheDirectory: true,
18 |         },
19 |       },
20 |       { test: /\.json$/, loader: 'json-loader' },
21 |     ],
22 |   },
23 |   resolve: {
24 |     symlinks: true,
25 |   },
26 |   output: {
27 |     libraryTarget: 'commonjs',
28 |     path: `${__dirname}/.webpack`,
29 |     filename: '[name].js',
30 |   },
31 |   externals: ['aws-sdk'],
32 |   plugins: [
33 |     new webpack.optimize.LimitChunkCountPlugin({
34 |       maxChunks: 1,
35 |     }),
36 |   ],
37 |   entry: slsw.lib.entries,
38 | }
39 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
  1 | {
  2 |   "name": "serverless-chrome",
  3 |   "version": "1.0.0-70",
  4 |   "description": "Run headless Chrome, serverless-ly",
  5 |   "author": "Marco Lüthy <marco.luethy@gmail.com (https://github.com/adieuadieu)",
  6 |   "maintainers": [],
  7 |   "contributors": [],
  8 |   "main": "handler.js",
  9 |   "engines": {
 10 |     "node": ">= 6.10.0"
 11 |   },
 12 |   "scripts": {
 13 |     "test": "npm run lint && nyc ava",
 14 |     "watch:test": "ava --watch",
 15 |     "ava": "ava",
 16 |     "lint": "npm run lint:eslint -s",
 17 |     "lint:eslint": "eslint --cache --ext .js .",
 18 |     "coverage": "nyc report",
 19 |     "upgrade-dependencies": "yarn upgrade-interactive --latest --exact",
 20 |     "precommit": "lint-staged",
 21 |     "prettier": "prettier-eslint --no-semi --single-quote --trailing-comma es5 --write",
 22 |     "release": "scripts/release.sh",
 23 |     "postversion": "scripts/sync-package-versions.sh"
 24 |   },
 25 |   "repository": {
 26 |     "type": "git",
 27 |     "url": "https://github.com/adieuadieu/serverless-chrome.git"
 28 |   },
 29 |   "keywords": [
 30 |     "serverless",
 31 |     "chrome",
 32 |     "chromium",
 33 |     "headless",
 34 |     "aws",
 35 |     "lambda",
 36 |     "serverless-framework"
 37 |   ],
 38 |   "license": "MIT",
 39 |   "bugs": {
 40 |     "url": "https://github.com/adieuadieu/serverless-chrome/issues"
 41 |   },
 42 |   "homepage": "https://github.com/adieuadieu/serverless-chrome",
 43 |   "dependencies": {},
 44 |   "devDependencies": {
 45 |     "ava": "0.25.0",
 46 |     "babel-core": "6.26.3",
 47 |     "babel-eslint": "8.2.3",
 48 |     "babel-plugin-transform-runtime": "6.23.0",
 49 |     "babel-polyfill": "6.26.0",
 50 |     "babel-preset-env": "1.7.0",
 51 |     "babel-preset-stage-3": "6.24.1",
 52 |     "babel-register": "6.26.0",
 53 |     "babel-runtime": "6.26.0",
 54 |     "codacy-coverage": "3.0.0",
 55 |     "coveralls": "3.0.1",
 56 |     "eslint": "4.19.1",
 57 |     "eslint-config-airbnb-base": "12.1.0",
 58 |     "eslint-plugin-ava": "4.5.1",
 59 |     "eslint-plugin-import": "2.11.0",
 60 |     "eslint-plugin-node": "6.0.1",
 61 |     "eslint-plugin-promise": "3.7.0",
 62 |     "eslint-tap": "2.0.1",
 63 |     "husky": "0.14.3",
 64 |     "lint-staged": "7.1.0",
 65 |     "nyc": "11.8.0",
 66 |     "prettier": "1.12.1",
 67 |     "prettier-eslint": "8.8.1",
 68 |     "prettier-eslint-cli": "4.7.1",
 69 |     "tap-xunit": "2.3.0"
 70 |   },
 71 |   "ava": {
 72 |     "require": "babel-register",
 73 |     "babel": "inherit"
 74 |   },
 75 |   "babel": {
 76 |     "sourceMaps": "inline",
 77 |     "plugins": [
 78 |       "transform-runtime"
 79 |     ],
 80 |     "presets": [
 81 |       [
 82 |         "env",
 83 |         {
 84 |           "targets": {
 85 |             "node": "6.10"
 86 |           }
 87 |         }
 88 |       ],
 89 |       "stage-3"
 90 |     ]
 91 |   },
 92 |   "eslintConfig": {
 93 |     "parser": "babel-eslint",
 94 |     "plugins": [
 95 |       "ava",
 96 |       "import"
 97 |     ],
 98 |     "extends": [
 99 |       "airbnb-base",
100 |       "plugin:ava/recommended"
101 |     ],
102 |     "settings": {
103 |       "import/parser": "babel-eslint",
104 |       "import/resolve": {
105 |         "moduleDirectory": [
106 |           "node_modules",
107 |           "src",
108 |           "./"
109 |         ]
110 |       }
111 |     },
112 |     "rules": {
113 |       "no-console": 0,
114 |       "semi": [
115 |         "error",
116 |         "never"
117 |       ],
118 |       "comma-dangle": [
119 |         "error",
120 |         "always-multiline"
121 |       ],
122 |       "space-before-function-paren": [
123 |         "error",
124 |         "always"
125 |       ]
126 |     }
127 |   },
128 |   "eslintIgnore": [
129 |     "node_modules",
130 |     "dist"
131 |   ],
132 |   "prettier": {
133 |     "printWidth": 80,
134 |     "eslintIntegration": true,
135 |     "jsonEnable": [],
136 |     "semi": false,
137 |     "singleQuote": true,
138 |     "trailingComma": "es5",
139 |     "useTabs": false
140 |   },
141 |   "lint-staged": {
142 |     "*.{js,jsx}": [
143 |       "yarn prettier",
144 |       "yarn lint",
145 |       "git add"
146 |     ]
147 |   }
148 | }
149 | 


--------------------------------------------------------------------------------
/packages/lambda/README.md:
--------------------------------------------------------------------------------
  1 | # serverless-chrome/lambda
  2 | 
  3 | Standalone package to run Headless Chrome on AWS Lambda's Node.js (6.10+) runtime.
  4 | 
  5 | [![npm](https://img.shields.io/npm/v/@serverless-chrome/lambda.svg?style=flat-square)](https://www.npmjs.com/package/@serverless-chrome/lambda)
  6 | 
  7 | 
  8 | ## Contents
  9 | 1. [Installation](#installation)
 10 | 1. [Setup](#setup)
 11 | 1. [Local Development](#local-development)
 12 | 1. [Framework Plugins](#framework-plugins)
 13 | 1. [Specifying Chromium Channel](#specifying-chromium-channel)
 14 | 
 15 | 
 16 | ## Installation
 17 | Install with yarn:
 18 | 
 19 | ```bash
 20 | yarn add @serverless-chrome/lambda
 21 | ```
 22 | 
 23 | Install with npm:
 24 | 
 25 | ```bash
 26 | npm install --save @serverless-chrome/lambda
 27 | ```
 28 | 
 29 | If you wish to develop locally, you also need to install `chrome-launcher`:
 30 | 
 31 | ```bash
 32 | npm install --save-dev chrome-launcher
 33 | ```
 34 | 
 35 | 
 36 | ## Setup
 37 | 
 38 | Use in your AWS Lambda function. Requires Node 6.10.
 39 | 
 40 | 
 41 | ```js
 42 | const launchChrome = require('@serverless-chrome/lambda')
 43 | const CDP = require('chrome-remote-interface')
 44 | 
 45 | module.exports.handler = function handler (event, context, callback) {
 46 |   launchChrome({
 47 |     flags: ['--window-size=1280,1696', '--hide-scrollbars']
 48 |   })
 49 |   .then((chrome) => {
 50 |     // Chrome is now running on localhost:9222
 51 | 
 52 |     CDP.Version()
 53 |       .then((versionInfo) => {
 54 |         callback(null, {
 55 |           versionInfo,
 56 |         })
 57 |       })
 58 |       .catch((error) => {
 59 |         callback(error)
 60 |       })
 61 |   })
 62 |   // Chrome didn't launch correctly 😢
 63 |   .catch(callback)
 64 | }
 65 | ```
 66 | 
 67 | 
 68 | ## Local Development
 69 | 
 70 | Local development is supported. In a non-lambda environment, the package will use chrome-launcher to launch a locally installed Chrome. You can also pass your own `chromePath`:
 71 | 
 72 | ```js
 73 | launchChrome({ chromePath: '/my/local/chrome/path' })
 74 | ```
 75 | 
 76 | **Command line flags (or "switches")**
 77 | 
 78 | The behavior of Chrome does vary between platforms. It may be necessary to experiment with flags to get the results you desire. On Lambda [default flags](/packages/lambda/src/flags.js) are used, but in development no default flags are used.
 79 | 
 80 | The package has zero external dependencies required for inclusion in your Lambda function's package.
 81 | 
 82 | 
 83 | ## Framework Plugins
 84 | 
 85 | There are plugins which bundle this package for easy deployment available for the following "serverless" frameworks:
 86 | 
 87 | - [serverless-plugin-chrome](/packages/serverless-plugin)
 88 | 
 89 | 
 90 | ## Specifying Chromium Channel
 91 | 
 92 | This package will use the latest stable-channel build of Headless Chromium for AWS Lambda. To select a different channel (beta or dev), export either an environment variable `NPM_CONFIG_CHROMIUM_CHANNEL` or add `chromiumChannel` to the `config` section of your `package.json`:
 93 | 
 94 | Your `package.json`:
 95 | 
 96 | ```json
 97 | {
 98 |   "name": "my-cool-project",
 99 |   "version": "1.0.0",
100 |   "config": {
101 |     "chromiumChannel": "dev"  <-- here
102 |   },
103 |   "scripts": {
104 | 
105 |   },
106 |   "description": {
107 | 
108 |   }
109 | }
110 | ```
111 | 
112 | Note: the `dev` channel is _almost_ `canary`, so use `dev` if you're looking for the Canary channel.
113 | 
114 | You can skip download entirely with `NPM_CONFIG_SERVERLESS_CHROME_SKIP_DOWNLOAD` or setting `skip_download` in the `config` section of your `package.json`
115 | 
116 | _Caution_: We test and develop features against the stable channel. Using the beta or dev channel versions of Chromium may lead to unexpected results, especially in relation to the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/) (which is used by tools like Chromeless and Puppeteer).
117 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/chromium/Dockerfile:
--------------------------------------------------------------------------------
 1 | #
 2 | # Launch headless Chromium with:
 3 | # docker run -d --rm --name headless-chromium -p 9222:9222 adieuadieu/headless-chromium-for-aws-lambda
 4 | #
 5 | 
 6 | #FROM amazonlinux:2017.03
 7 | FROM lambci/lambda
 8 | 
 9 | # ref: https://chromium.googlesource.com/chromium/src.git/+refs
10 | ARG VERSION
11 | ENV VERSION ${VERSION:-master}
12 | 
13 | LABEL maintainer="Marco Lüthy <marco.luethy@gmail.com>"
14 | LABEL chromium="${VERSION}"
15 | 
16 | WORKDIR /
17 | 
18 | ADD dist/headless-chromium /bin/headless-chromium
19 | 
20 | EXPOSE 9222
21 | 
22 | ENTRYPOINT [ \
23 |   "/bin/headless-chromium", \
24 |   "--disable-dev-shm-usage", \
25 |   "--disable-gpu", \
26 |   "--no-sandbox", \
27 |   "--hide-scrollbars", \
28 |   "--remote-debugging-address=0.0.0.0", \
29 |   "--remote-debugging-port=9222" \
30 |   ]
31 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/chromium/README.md:
--------------------------------------------------------------------------------
  1 | # Build Headless Chromium for AWS Lambda
  2 | 
  3 | Documentation has moved [here](docs/chrome.md)
  4 | 
  5 | If you're looking for instructions on how to compile/build Chromium/Chrome for AWS Lambda have a look at the [build script](packages/lambda/builds/chromium/build/build.sh) or the [Dockerfile](packages/lambda/builds/chromium/build/Dockerfile) or simply use the built [Docker image](https://hub.docker.com/r/adieuadieu/headless-chromium-for-aws-lambda/):
  6 | 
  7 | ```bash
  8 | docker run -d --rm \
  9 |   --name headless-chromium \
 10 |   -p 9222:9222 \
 11 |   adieuadieu/headless-chromium-for-aws-lambda
 12 | ```
 13 | 
 14 | Headless Chromium is now running and accessible:
 15 | 
 16 | ```
 17 | GET http://localhost:9222/
 18 | ```
 19 | 
 20 | 
 21 | 
 22 | **Note:** to successfully build the Docker image on MacOS, you may need to [increase the size](https://community.hortonworks.com/articles/65901/how-to-increase-the-size-of-the-base-docker-for-ma.html) of the Docker data sparse image. *Warning!:* This will wipe out all of your local images/containers.
 23 | 
 24 | 
 25 | ```bash
 26 | rm ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2
 27 | qemu-img create -f qcow2 ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2 50G
 28 | ```
 29 | 
 30 | 
 31 | ----
 32 | 
 33 | <br/>
 34 | <br/>
 35 | <br/>
 36 | <br/>
 37 | <br/>
 38 | The rest of this README is outdated.
 39 | <br/>
 40 | <br/>
 41 | <br/>
 42 | <br/>
 43 | <br/>
 44 | 
 45 | ----
 46 | 
 47 | # What is this?
 48 | 
 49 | `chrome-headless-lambda-linux-x64.tar.gz` was created on a Linux machine. It contains [Headless Chrome](https://cs.chromium.org/chromium/src/headless/app/) binaries specific to the [Lambda execution environment](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html). The tarball is used during the Serverless deployment/packaging step to create the zip file for deploying the function to Lambda.
 50 | 
 51 | 
 52 | ## Building Headless Chrome for AWS Lambda
 53 | 
 54 | How to build headless_shell (headless Chrome) for the lambda execution environment. These steps are based on [this](http://www.zackarychapple.guru/chrome/2016/08/24/chrome-headless.html) and [this](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md).
 55 | 
 56 | 1. Create a new EC2 instance using the community AMI with name amzn-ami-hvm-2016.03.3.x86_64-gp2 (us-west-2 ami-7172b611).
 57 | 2. Pick an Instance Type with at least 16 GB of memory. Compile time will take about 4-5 hours on a t2.xlarge, or 2-3ish on a t2.2xlarge or about 45 min on a c4.4xlarge.
 58 | 3. Give yourself a Root Volume that's at least 30 GB (40 GB if you want to compile a debug build—which you won't be able to upload to Lambda because it's too big.)
 59 | 4. SSH into the new instance and run:
 60 | 
 61 | ```bash
 62 | sudo printf "LANG=en_US.utf-8\nLC_ALL=en_US.utf-8" >> /etc/environment
 63 | 
 64 | sudo yum install -y git redhat-lsb python bzip2 tar pkgconfig atk-devel alsa-lib-devel bison binutils brlapi-devel bluez-libs-devel bzip2-devel cairo-devel cups-devel dbus-devel dbus-glib-devel expat-devel fontconfig-devel freetype-devel gcc-c++ GConf2-devel glib2-devel glibc.i686 gperf glib2-devel gtk2-devel gtk3-devel java-1.*.0-openjdk-devel libatomic libcap-devel libffi-devel libgcc.i686 libgnome-keyring-devel libjpeg-devel libstdc++.i686 libX11-devel libXScrnSaver-devel libXtst-devel libxkbcommon-x11-devel ncurses-compat-libs nspr-devel nss-devel pam-devel pango-devel pciutils-devel pulseaudio-libs-devel zlib.i686 httpd mod_ssl php php-cli python-psutil wdiff --enablerepo=epel
 65 | ```
 66 | 
 67 | _Yum_ will complain about some packages not existing. Whatever. I haven't looked into them. Didn't seem to stop me from building headless_shell, though. Ignore whiney little _Yum_ and move on. Next:
 68 | 
 69 | ```bash
 70 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
 71 | echo "export PATH=$PATH:$HOME/depot_tools" >> ~/.bash_profile
 72 | source ~/.bash_profile
 73 | mkdir Chromium && cd Chromium
 74 | fetch --no-history chromium
 75 | cd src
 76 | ```
 77 | 
 78 | **TODO:** add part here about modifying Chrome to not use /dev/shm. See here: https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/CPInd55OBgAJ
 79 | 
 80 | 
 81 | ```bash
 82 | mkdir -p out/Headless
 83 | echo 'import("//build/args/headless.gn")' > out/Headless/args.gn
 84 | echo 'is_debug = false' >> out/Headless/args.gn
 85 | echo 'symbol_level = 0' >> out/Headless/args.gn
 86 | echo 'is_component_build = false' >> out/Headless/args.gn
 87 | echo 'remove_webcore_debug_symbols = true' >> out/Headless/args.gn
 88 | echo 'enable_nacl = false' >> out/Headless/args.gn
 89 | gn gen out/Headless
 90 | ninja -C out/Headless headless_shell
 91 | ```
 92 | 
 93 | make the tarball:
 94 | ```bash
 95 | mkdir out/headless-chrome && cd out
 96 | cp Headless/headless_shell Headless/libosmesa.so headless-chrome/
 97 | tar -zcvf chrome-headless-lambda-linux-x64.tar.gz headless-chrome/
 98 | zip headless-chrome chrome-headless-lambda-linux-x64.zip
 99 | ```
100 | 
101 | ```
102 | scp -i path/to/your/key-pair.pem ec2-user@<the-instance-public-ip>:/home/ec2-user/Chromium/src/out/chrome-headless-lambda-linux-x64.zip ./
103 | ```
104 | 
105 | 
106 | **TODO:** We don't need `libosmesa.so` cuz we're not using the GPU? See here: https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/XMKlEMP3EQAJ
107 | 
108 | 
109 | ## Updating
110 | 
111 | ```bash
112 | git fetch --tags
113 | gclient sync --jobs 16
114 | ```
115 | 
116 | https://omahaproxy.appspot.com/
117 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/chromium/build/.gclient:
--------------------------------------------------------------------------------
 1 | solutions = [
 2 |   {
 3 |     "managed": False,
 4 |     "name": "src",
 5 |     "url": "https://chromium.googlesource.com/chromium/src.git",
 6 |     "custom_deps": {},
 7 |     "deps_file": ".DEPS.git",
 8 |     "safesync_url": ""
 9 |   }
10 | ]
11 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/chromium/build/Dockerfile:
--------------------------------------------------------------------------------
 1 | #
 2 | # Build with:
 3 | # docker build --compress -t adieuadieu/chromium-for-amazonlinux-base:62.0.3202.62 --build-arg VERSION=62.0.3202.62 .
 4 | #
 5 | # Jump into the container with:
 6 | # docker run -i -t --rm --entrypoint /bin/bash  adieuadieu/chromium-for-amazonlinux-base
 7 | #
 8 | # Launch headless Chromium with:
 9 | # docker run -d --rm --name headless-chromium -p 9222:9222 adieuadieu/headless-chromium-for-aws-lambda
10 | #
11 | 
12 | FROM amazonlinux:2.0.20200722.0-with-sources
13 | 
14 | # ref: https://chromium.googlesource.com/chromium/src.git/+refs
15 | ARG VERSION
16 | ENV VERSION ${VERSION:-master}
17 | 
18 | LABEL maintainer="Marco Lüthy <marco.luethy@gmail.com>"
19 | LABEL chromium="${VERSION}"
20 | 
21 | WORKDIR /
22 | 
23 | ADD build.sh /
24 | ADD .gclient /build/chromium/
25 | 
26 | RUN sh /build.sh
27 | 
28 | EXPOSE 9222
29 | 
30 | ENTRYPOINT [ \
31 |   "/bin/headless-chromium", \
32 |   "--disable-dev-shm-usage", \
33 |   "--disable-gpu", \
34 |   "--no-sandbox", \
35 |   "--hide-scrollbars", \
36 |   "--remote-debugging-address=0.0.0.0", \
37 |   "--remote-debugging-port=9222" \
38 |   ]
39 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/chromium/build/build.sh:
--------------------------------------------------------------------------------
  1 | #!/bin/sh
  2 | # shellcheck shell=dash
  3 | 
  4 | #
  5 | # Build Chromium for Amazon Linux.
  6 | # Assumes root privileges. Or, more likely, Docker—take a look at
  7 | # the corresponding Dockerfile in this directory.
  8 | #
  9 | # Requires
 10 | #
 11 | # Usage: ./build.sh
 12 | #
 13 | # Further documentation: https://github.com/adieuadieu/serverless-chrome/blob/develop/docs/chrome.md
 14 | #
 15 | 
 16 | set -e
 17 | 
 18 | BUILD_BASE=$(pwd)
 19 | VERSION=${VERSION:-master}
 20 | 
 21 | printf "LANG=en_US.utf-8\nLC_ALL=en_US.utf-8" >> /etc/environment
 22 | 
 23 | # install dependencies
 24 | yum groupinstall -y "Development Tools"
 25 | yum install -y \
 26 |   alsa-lib-devel atk-devel binutils bison bluez-libs-devel brlapi-devel \
 27 |   bzip2 bzip2-devel cairo-devel cmake cups-devel dbus-devel dbus-glib-devel \
 28 |   expat-devel fontconfig-devel freetype-devel gcc-c++ git glib2-devel glibc \
 29 |   gperf gtk3-devel htop httpd java-1.*.0-openjdk-devel libatomic libcap-devel \
 30 |   libffi-devel libgcc libgnome-keyring-devel libjpeg-devel libstdc++ libuuid-devel \
 31 |   libX11-devel libxkbcommon-x11-devel libXScrnSaver-devel libXtst-devel mercurial \
 32 |   mod_ssl ncurses-compat-libs nspr-devel nss-devel pam-devel pango-devel \
 33 |   pciutils-devel php php-cli pkgconfig pulseaudio-libs-devel python python3 \
 34 |   tar zlib zlib-devel
 35 | 
 36 | mkdir -p build/chromium
 37 | 
 38 | cd build
 39 | 
 40 | # install dept_tools
 41 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
 42 | 
 43 | export PATH="/opt/gtk/bin:$PATH:$BUILD_BASE/build/depot_tools"
 44 | 
 45 | cd chromium
 46 | 
 47 | # fetch chromium source code
 48 | # ref: https://www.chromium.org/developers/how-tos/get-the-code/working-with-release-branches
 49 | 
 50 | # git shallow clone, much quicker than a full git clone; see https://stackoverflow.com/a/39067940/3145038 for more details
 51 | 
 52 | git clone --branch "$VERSION" --depth 1 https://chromium.googlesource.com/chromium/src.git
 53 | 
 54 | # Checkout all the submodules at their branch DEPS revisions
 55 | gclient sync --with_branch_heads --jobs 16
 56 | 
 57 | cd src
 58 | 
 59 | # the following is no longer necessary since. left here for nostalgia or something.
 60 | # ref: https://chromium.googlesource.com/chromium/src/+/1824e5752148268c926f1109ed7e5ef1d937609a%5E%21
 61 | # tweak to disable use of the tmpfs mounted at /dev/shm
 62 | # sed -e '/if (use_dev_shm) {/i use_dev_shm = false;\n' -i base/files/file_util_posix.cc
 63 | 
 64 | #
 65 | # tweak to keep Chrome from crashing after 4-5 Lambda invocations
 66 | # see https://github.com/adieuadieu/serverless-chrome/issues/41#issuecomment-340859918
 67 | # Thank you, Geert-Jan Brits (@gebrits)!
 68 | #
 69 | SANDBOX_IPC_SOURCE_PATH="content/browser/sandbox_ipc_linux.cc"
 70 | 
 71 | sed -e 's/PLOG(WARNING) << "poll";/PLOG(WARNING) << "poll"; failed_polls = 0;/g' -i "$SANDBOX_IPC_SOURCE_PATH"
 72 | 
 73 | 
 74 | # specify build flags
 75 | mkdir -p out/Headless && \
 76 |   echo 'import("//build/args/headless.gn")' > out/Headless/args.gn && \
 77 |   echo 'is_debug = false' >> out/Headless/args.gn && \
 78 |   echo 'symbol_level = 0' >> out/Headless/args.gn && \
 79 |   echo 'is_component_build = false' >> out/Headless/args.gn && \
 80 |   echo 'remove_webcore_debug_symbols = true' >> out/Headless/args.gn && \
 81 |   echo 'enable_nacl = false' >> out/Headless/args.gn && \
 82 |   gn gen out/Headless
 83 | 
 84 | # build chromium headless shell
 85 | ninja -C out/Headless headless_shell
 86 | 
 87 | cp out/Headless/headless_shell "$BUILD_BASE/bin/headless-chromium-unstripped"
 88 | 
 89 | cd "$BUILD_BASE"
 90 | 
 91 | # strip symbols
 92 | strip -o "$BUILD_BASE/bin/headless-chromium" build/chromium/src/out/Headless/headless_shell
 93 | 
 94 | # Use UPX to package headless chromium
 95 | # this adds 1-1.5 seconds of startup time so generally
 96 | # not so great for use in AWS Lambda so we don't actually use it
 97 | # but left here in case someone finds it useful
 98 | # yum install -y ucl ucl-devel --enablerepo=epel
 99 | # cd build
100 | # git clone https://github.com/upx/upx.git
101 | # cd build/upx
102 | # git submodule update --init --recursive
103 | # make all
104 | # cp "$BUILD_BASE/build/chromium/src/out/Headless/headless_shell" "$BUILD_BASE/bin/headless-chromium-packaged"
105 | # src/upx.out "$BUILD_BASE/bin/headless-chromium-packaged"
106 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/chromium/latest.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Get current versions of Chromium
 6 | #
 7 | # Requires jq
 8 | #
 9 | # Usage: ./latest.sh [stable|beta|dev]
10 | #
11 | 
12 | CHANNEL=${1:-stable}
13 | 
14 | VERSION=$(curl -s https://omahaproxy.appspot.com/all.json | \
15 |   jq -r ".[] | select(.os == \"linux\") | .versions[] | select(.channel == \"$CHANNEL\") | .current_version" \
16 | )
17 | 
18 | echo "$VERSION"
19 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/chromium/version.json:
--------------------------------------------------------------------------------
1 | {
2 |   "beta": "90.0.4430.51",
3 |   "dev": "91.0.4464.5",
4 |   "stable": "89.0.4389.114"
5 | }
6 | 


--------------------------------------------------------------------------------
/packages/lambda/builds/firefox/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adieuadieu/serverless-chrome/4c5894dd888a4bc58c16dafd520c318363c461dc/packages/lambda/builds/firefox/README.md


--------------------------------------------------------------------------------
/packages/lambda/builds/nss/README.md:
--------------------------------------------------------------------------------
 1 | # NSS
 2 | 
 3 | NSS is a library required by chrome to run. Unfortunately the version provided
 4 | by the AWS lambda image is too old, so it's necessary to build it.
 5 | 
 6 | *Note:* This work was originally done by @qubyte in [PR#56](https://github.com/adieuadieu/serverless-chrome/pull/56/files). It's currently not necessary to include a special version of NSS but may become necessary again in the future—for that reason this is left here.
 7 | 
 8 | ## Building
 9 | 
10 | Start a smallish EC2 instance using the same AMI as lambda. You can find a link
11 | to it [here][1]. Build on the instance using the following (adapted from
12 | instructions found [here][2]):
13 | 
14 | ```shell
15 | sudo yum install mercurial
16 | sudo yum groupinstall 'Development Tools'
17 | sudo yum install zlib-devel
18 | 
19 | hg clone https://hg.mozilla.org/projects/nspr
20 | hg clone https://hg.mozilla.org/projects/nss
21 | 
22 | cd nss
23 | 
24 | export BUILD_OPT=1
25 | export USE_64=1
26 | export NSDISTMODE=copy
27 | 
28 | gmake nss_build_all
29 | ```
30 | 
31 | Remove any simlinks in the `dist` directory (they'll be links to .chk files) and
32 | zip it up for grabbing by scp. Place the archive in this folder (I've used a
33 | reverse date and the commit hash as a name), and replace the name in the
34 | lambda package file.
35 | 
36 | [1]: http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html
37 | [2]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Reference/Building_and_installing_NSS/Build_instructions
38 | 


--------------------------------------------------------------------------------
/packages/lambda/chrome/chrome-headless-lambda-linux-59.0.3039.0.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adieuadieu/serverless-chrome/4c5894dd888a4bc58c16dafd520c318363c461dc/packages/lambda/chrome/chrome-headless-lambda-linux-59.0.3039.0.tar.gz


--------------------------------------------------------------------------------
/packages/lambda/chrome/chrome-headless-lambda-linux-60.0.3089.0.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adieuadieu/serverless-chrome/4c5894dd888a4bc58c16dafd520c318363c461dc/packages/lambda/chrome/chrome-headless-lambda-linux-60.0.3089.0.tar.gz


--------------------------------------------------------------------------------
/packages/lambda/chrome/chrome-headless-lambda-linux-60.0.3095.0.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adieuadieu/serverless-chrome/4c5894dd888a4bc58c16dafd520c318363c461dc/packages/lambda/chrome/chrome-headless-lambda-linux-60.0.3095.0.zip


--------------------------------------------------------------------------------
/packages/lambda/chrome/chrome-headless-lambda-linux-x64.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adieuadieu/serverless-chrome/4c5894dd888a4bc58c16dafd520c318363c461dc/packages/lambda/chrome/chrome-headless-lambda-linux-x64.zip


--------------------------------------------------------------------------------
/packages/lambda/chrome/headless-chromium-64.0.3242.2-amazonlinux-2017-03.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adieuadieu/serverless-chrome/4c5894dd888a4bc58c16dafd520c318363c461dc/packages/lambda/chrome/headless-chromium-64.0.3242.2-amazonlinux-2017-03.zip


--------------------------------------------------------------------------------
/packages/lambda/index.d.ts:
--------------------------------------------------------------------------------
 1 | export default function ServerlessChromeLambda(options?: Option): Promise<ServerLessChrome>;
 2 | 
 3 | type Option = {
 4 |   flags?: string[],
 5 |   chromePath?: string,
 6 |   port?: number,
 7 |   forceLambdaLauncher?: boolean,
 8 | };
 9 | 
10 | type ServerLessChrome = {
11 |   pid: number,
12 |   port: number,
13 |   url: string,
14 |   log: string,
15 |   errorLog: string,
16 |   pidFile: string,
17 |   metaData: {
18 |     launchTime: number,
19 |     didLaunch: boolean,
20 |   },
21 |   kill: () => void,
22 | };
23 | 


--------------------------------------------------------------------------------
/packages/lambda/integration-test/dist:
--------------------------------------------------------------------------------
1 | ../dist/


--------------------------------------------------------------------------------
/packages/lambda/integration-test/handler.js:
--------------------------------------------------------------------------------
 1 | const path = require('path')
 2 | const chrome = require('./dist/bundle.cjs.js')
 3 | const cdp = require('chrome-remote-interface')
 4 | 
 5 | module.exports.run = async function run (event) {
 6 |   const channel = `${event.channel}-` || ''
 7 | 
 8 |   console.log('started. Channel:', channel)
 9 | 
10 |   try {
11 |     const instance = await chrome({
12 |       chromePath: path.resolve(__dirname, `./dist/${channel}headless-chromium`),
13 |     })
14 | 
15 |     console.log('we got here. sweet.', instance)
16 | 
17 |     return {
18 |       statusCode: 200,
19 |       body: JSON.stringify({
20 |         event,
21 |         instance,
22 |         versionInfo: await cdp.Version(),
23 |       }),
24 |       headers: {
25 |         'Content-Type': 'application/json',
26 |       },
27 |     }
28 |   } catch (error) {
29 |     console.log('error', error)
30 | 
31 |     return {
32 |       statusCode: 200,
33 |       body: JSON.stringify({
34 |         error,
35 |       }),
36 |       headers: {
37 |         'Content-Type': 'application/json',
38 |       },
39 |     }
40 |   }
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/lambda/integration-test/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "integration-test",
 3 |   "version": "0.0.0",
 4 |   "private": true,
 5 |   "description": "",
 6 |   "main": "handler.js",
 7 |   "dependencies": {
 8 |     "chrome-remote-interface": "0.25.5"
 9 |   },
10 |   "devDependencies": {
11 |     "serverless": "1.27.2"
12 |   },
13 |   "scripts": {
14 |     "test": "echo \"Error: no test specified\" && exit 1"
15 |   },
16 |   "author": "",
17 |   "license": "ISC"
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/lambda/integration-test/serverless.yml:
--------------------------------------------------------------------------------
 1 | service: serverless-chrome-lambda-pkg-test
 2 | 
 3 | provider:
 4 |   name: aws
 5 |   runtime: nodejs8.10
 6 |   stage: dev
 7 |   region: us-east-1
 8 |   environment:
 9 |     DEBUG: "*"
10 | 
11 | functions:
12 |   test:
13 |     description: serverless-chrome/lambda test
14 |     memorySize: 1536
15 |     timeout: 30
16 |     handler: handler.run
17 | 
18 |     events:
19 |       - http:
20 |           path: package/lambda/test
21 |           method: get
22 | 


--------------------------------------------------------------------------------
/packages/lambda/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@serverless-chrome/lambda",
 3 |   "version": "1.0.0-70",
 4 |   "description": "Run headless Chrome/Chromium on AWS Lambda",
 5 |   "author": "Marco Lüthy",
 6 |   "keywords": [
 7 |     "serverless",
 8 |     "chrome",
 9 |     "chromium",
10 |     "headless",
11 |     "aws",
12 |     "lambda"
13 |   ],
14 |   "main": "dist/bundle.cjs.js",
15 |   "module": "dist/bundle.es.js",
16 |   "files": [
17 |     "dist/bundle.cjs.js",
18 |     "dist/bundle.es.js",
19 |     "scripts/postinstall.js"
20 |   ],
21 |   "types": "index.d.ts",
22 |   "repository": "git@github.com:adieuadieu/serverless-chrome.git",
23 |   "bugs": {
24 |     "url": "https://github.com/adieuadieu/serverless-chrome/issues"
25 |   },
26 |   "homepage": "https://github.com/adieuadieu/serverless-chrome/tree/master/packages/lambda",
27 |   "license": "MIT",
28 |   "engines": {
29 |     "node": ">= 6.10"
30 |   },
31 |   "config": {
32 |     "jsSrc": "src/"
33 |   },
34 |   "scripts": {
35 |     "clean": "rm -Rf dist/ ./**.zip",
36 |     "test": "npm run test:integration",
37 |     "test:integration": "scripts/test-integration.sh",
38 |     "build": "rollup -c",
39 |     "dev": "rollup -c -w",
40 |     "prepublishOnly": "npm run clean && npm run build",
41 |     "postinstall": "node scripts/postinstall.js",
42 |     "package-binaries": "scripts/package-binaries.sh",
43 |     "latest-browser-versions": "scripts/latest-versions.sh",
44 |     "upgrade-dependencies": "yarn upgrade-interactive --latest --exact"
45 |   },
46 |   "dependencies": {
47 |     "extract-zip": "1.6.6"
48 |   },
49 |   "devDependencies": {
50 |     "ava": "0.25.0",
51 |     "babel-core": "6.26.3",
52 |     "babel-preset-env": "1.7.0",
53 |     "babel-register": "6.26.0",
54 |     "chrome-launcher": "0.10.2",
55 |     "rollup": "0.59.1",
56 |     "rollup-plugin-babel": "3.0.4",
57 |     "rollup-plugin-node-resolve": "3.3.0"
58 |   },
59 |   "babel": {
60 |     "sourceMaps": true,
61 |     "presets": [
62 |       [
63 |         "env",
64 |         {
65 |           "modules": "commonjs",
66 |           "targets": {
67 |             "node": "6.10"
68 |           }
69 |         }
70 |       ]
71 |     ]
72 |   }
73 | }
74 | 


--------------------------------------------------------------------------------
/packages/lambda/rollup.config.js:
--------------------------------------------------------------------------------
 1 | import babel from 'rollup-plugin-babel'
 2 | import resolve from 'rollup-plugin-node-resolve'
 3 | 
 4 | export default {
 5 |   input: 'src/index.js',
 6 |   output: [
 7 |     { file: 'dist/bundle.cjs.js', format: 'cjs' },
 8 |     { file: 'dist/bundle.es.js', format: 'es' },
 9 |   ],
10 |   plugins: [
11 |     resolve({
12 |       // module: true, // Default: true
13 |       // jsnext: true, // Default: false
14 |       // main: true, // Default: true
15 |       extensions: ['.js'], // Default: ['.js']
16 |       // Lock the module search in this path (like a chroot). Module defined
17 |       // outside this path will be mark has external
18 |       // jail: './', // Default: '/'
19 |       // If true, inspect resolved files to check that they are
20 |       // ES2015 modules
21 |       // modulesOnly: true, // Default: false
22 |     }),
23 |     babel({
24 |       babelrc: false,
25 |       presets: [
26 |         [
27 |           'env',
28 |           {
29 |             modules: false,
30 |             targets: {
31 |               node: '6.10',
32 |             },
33 |           },
34 |         ],
35 |       ],
36 |     }),
37 |   ],
38 |   external: ['fs', 'child_process', 'net', 'http', 'path', 'chrome-launcher'],
39 | }
40 | 


--------------------------------------------------------------------------------
/packages/lambda/scripts/latest-versions.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Get current versions of browsers
 6 | #
 7 | # Requires jq
 8 | #
 9 | # Usage: ./latest-versions.sh [chromium|firefox]
10 | #
11 | 
12 | set -e
13 | 
14 | cd "$(dirname "$0")/.."
15 | 
16 | PACKAGE_DIRECTORY=$(pwd)
17 | 
18 | version() {
19 |   BUILD_NAME=$1
20 |   
21 |   cd "$PACKAGE_DIRECTORY/builds/$BUILD_NAME"
22 | 
23 |   echo "--- $BUILD_NAME ---"
24 | 
25 |   CHANNEL_LIST=$(jq -r ". | keys | tostring" ./version.json | sed -e 's/[^A-Za-z,]//g' | tr , '\n')
26 | 
27 |   while IFS= read -r CHANNEL; do
28 |     # Skip empty lines and lines starting with a hash (#):
29 |     [ -z "$CHANNEL" ] || [ "${CHANNEL#\#}" != "$CHANNEL" ] && continue
30 | 
31 |     OUR_VERSION=$(jq -r ".$CHANNEL" version.json)
32 |     LATEST_VERSION=$(./latest.sh "$CHANNEL")
33 | 
34 |     if [ "$OUR_VERSION" != "$LATEST_VERSION" ]; then
35 |       STATUS="new; our version is $OUR_VERSION"
36 |     else
37 |       STATUS="current"
38 |     fi
39 | 
40 |     echo "$CHANNEL: $LATEST_VERSION ($STATUS)"
41 |   done << EOL
42 | $CHANNEL_LIST
43 | EOL
44 | }
45 | 
46 | if [ ! -z "$1" ]; then
47 |   version "$1"
48 | else
49 |   cd "$PACKAGE_DIRECTORY/builds"
50 | 
51 |   for DOCKER_FILE in */latest.sh; do
52 |     version "${DOCKER_FILE%%/*}"
53 |   done
54 | fi
55 | 


--------------------------------------------------------------------------------
/packages/lambda/scripts/package-binaries.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Builds specified or all headless browsers (chromium, firefox) version defined in package.json
 6 | #
 7 | # Requires Docker, jq, and zip
 8 | #
 9 | # Usage: ./build-binaries.sh [chromium|firefox] [stable|beta|dev]
10 | #
11 | 
12 | set -e
13 | 
14 | cd "$(dirname "$0")/.."
15 | 
16 | PACKAGE_DIRECTORY=$(pwd)
17 | 
18 | packageBinary() {
19 |   BUILD_NAME=$1
20 |   CHANNEL=$2
21 | 
22 |   cd "$PACKAGE_DIRECTORY/builds/$BUILD_NAME"
23 |   
24 |   DOCKER_IMAGE=headless-$BUILD_NAME-for-aws-lambda
25 |   VERSION=$(jq -r ".$CHANNEL" version.json)
26 |   BUILD_PATH="dist/$BUILD_NAME"
27 |   ZIPFILE_PATH="$CHANNEL-headless-$BUILD_NAME-$VERSION-amazonlinux-2017-03.zip"
28 | 
29 |   if [ ! -f "dist/$ZIPFILE_PATH" ]; then
30 |     echo "Packaging $BUILD_NAME version $VERSION ($CHANNEL)"
31 |     
32 |     mkdir -p "$BUILD_PATH"
33 | 
34 |     # Extract binary from docker image
35 |     docker run -dt --rm --name "$DOCKER_IMAGE" "adieuadieu/$DOCKER_IMAGE:$VERSION"
36 |     docker cp "$DOCKER_IMAGE":/bin/headless-"$BUILD_NAME" "$BUILD_PATH"
37 |     docker stop "$DOCKER_IMAGE"
38 | 
39 |     # Package
40 |     cd "$BUILD_PATH"
41 |     zip -9 -D "../$ZIPFILE_PATH" "headless-$BUILD_NAME"
42 |     cd ../../
43 | 
44 |     # stick a copy in packages' dist/ for tests and local dev
45 |     mkdir -p "$PACKAGE_DIRECTORY/dist"
46 |     cp "$BUILD_PATH/headless-$BUILD_NAME" "$PACKAGE_DIRECTORY/dist/$CHANNEL-headless-$BUILD_NAME"
47 | 
48 |     # Cleanup
49 |     rm -Rf "$BUILD_PATH"
50 |   else
51 |     echo "$BUILD_NAME version $VERSION was previously package. Skipping."
52 |   fi
53 | }
54 | 
55 | # main script
56 | 
57 | if [ ! -z "$1" ]; then
58 |   packageBinary "$1" "$2"
59 | else
60 |   cd "$PACKAGE_DIRECTORY/builds"
61 | 
62 |   for DOCKER_FILE in */Dockerfile; do
63 |     packageBinary "${DOCKER_FILE%%/*}" stable
64 |     packageBinary "${DOCKER_FILE%%/*}" beta
65 |     packageBinary "${DOCKER_FILE%%/*}" dev
66 |   done
67 | fi
68 | 


--------------------------------------------------------------------------------
/packages/lambda/scripts/postinstall.js:
--------------------------------------------------------------------------------
  1 | /*
  2 | @TODO: checksum/crc check on archive using pkg.config.tarballChecksum
  3 | */
  4 | const fs = require('fs')
  5 | const path = require('path')
  6 | const https = require('https')
  7 | const extract = require('extract-zip')
  8 | 
  9 | if (
 10 |   process.env.NPM_CONFIG_SERVERLESS_CHROME_SKIP_DOWNLOAD ||
 11 |   process.env.npm_config_serverless_chrome_skip_download
 12 | ) {
 13 |   console.warn('Skipping Chromium download. ' +
 14 |       '"NPM_CONFIG_SERVERLESS_CHROME_SKIP_DOWNLOAD" was set in environment or NPM config.')
 15 |   return
 16 | }
 17 | 
 18 | const CHROMIUM_CHANNEL =
 19 |   process.env.NPM_CONFIG_CHROMIUM_CHANNEL ||
 20 |   process.env.npm_config_chromiumChannel ||
 21 |   process.env.npm_config_chromium_channel ||
 22 |   'stable'
 23 | 
 24 | if (CHROMIUM_CHANNEL !== 'stable') {
 25 |   console.warn(`Downloading "${CHROMIUM_CHANNEL}" version of Chromium because` +
 26 |       ' "NPM_CONFIG_CHROMIUM_CHANNEL" was set in environment or NPM config.')
 27 | }
 28 | 
 29 | const RELEASE_DOWNLOAD_URL_BASE =
 30 |   'https://github.com/adieuadieu/serverless-chrome/releases/download'
 31 | 
 32 | function unlink (filePath) {
 33 |   return new Promise((resolve, reject) => {
 34 |     fs.unlink(filePath, error => (error ? reject(error) : resolve()))
 35 |   })
 36 | }
 37 | 
 38 | function extractFile (file, destination) {
 39 |   return new Promise((resolve, reject) => {
 40 |     extract(
 41 |       file,
 42 |       { dir: destination },
 43 |       error => (error ? reject(error) : resolve())
 44 |     )
 45 |   })
 46 | }
 47 | 
 48 | function get (url, destination) {
 49 |   return new Promise((resolve, reject) => {
 50 |     https
 51 |       .get(url, (response) => {
 52 |         if (response.statusCode >= 300 && response.statusCode <= 400) {
 53 |           return get(response.headers.location, destination)
 54 |             .then(resolve)
 55 |             .catch(reject)
 56 |         } else if (response.statusCode !== 200) {
 57 |           fs.unlink(destination, () => null)
 58 |           return reject(new Error(`HTTP ${response.statusCode}: Could not download ${url}`))
 59 |         }
 60 | 
 61 |         const file = fs.createWriteStream(destination)
 62 | 
 63 |         response.pipe(file)
 64 | 
 65 |         return file.on('finish', () => {
 66 |           file.close(resolve)
 67 |         })
 68 |       })
 69 |       .on('error', (error) => {
 70 |         fs.unlink(destination, () => null)
 71 |         reject(error)
 72 |       })
 73 |   })
 74 | }
 75 | 
 76 | function getChromium () {
 77 |   const ZIP_FILENAME = `${CHROMIUM_CHANNEL}-headless-chromium-amazonlinux-2.zip`
 78 |   const ZIP_URL = `${RELEASE_DOWNLOAD_URL_BASE}/v${
 79 |     process.env.npm_package_version
 80 |   }/${ZIP_FILENAME}`
 81 |   const DOWNLOAD_PATH = path.resolve(__dirname, '..', ZIP_FILENAME)
 82 |   const EXTRACT_PATH = path.resolve(__dirname, '..', 'dist')
 83 | 
 84 |   if (fs.existsSync(path.resolve(EXTRACT_PATH, 'headless-chromium'))) {
 85 |     console.log('Precompiled headless Chromium binary for AWS Lambda previously downloaded. Skipping download.')
 86 |     return Promise.resolve()
 87 |   }
 88 | 
 89 |   console.log(`Downloading precompiled headless Chromium binary (${CHROMIUM_CHANNEL} channel) for AWS Lambda.`)
 90 | 
 91 |   return get(ZIP_URL, DOWNLOAD_PATH)
 92 |     .then(() => extractFile(DOWNLOAD_PATH, EXTRACT_PATH))
 93 |     .then(() => console.log('Completed Headless Chromium download.'))
 94 |     .then(() => unlink(DOWNLOAD_PATH))
 95 | }
 96 | 
 97 | if (require.main === module) {
 98 |   getChromium().catch((error) => {
 99 |     console.error(error)
100 |     process.exit(1)
101 |   })
102 | }
103 | 
104 | module.exports = {
105 |   get,
106 |   extractFile,
107 | }
108 | 


--------------------------------------------------------------------------------
/packages/lambda/scripts/test-integration.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Runs a simple integration-test lambda handler in Docker
 6 | #
 7 | # Requires Docker
 8 | #
 9 | # Usage: ./integration-test.sh [stable|beta|dev]
10 | #
11 | 
12 | set -e
13 | 
14 | cd "$(dirname "$0")/.."
15 | 
16 | CHANNEL=${1:-stable}
17 | 
18 | if [ ! -d "dist/" ]; then
19 |   ./scripts/package-binaries.sh chromium "$CHANNEL"
20 |   npm run build
21 | fi
22 | 
23 | (cd integration-test &&  npm install)
24 | 
25 | docker run \
26 |   -v "$PWD/integration-test":/var/task \
27 |   -v "$PWD/dist":/var/task/dist \
28 |   lambci/lambda:nodejs8.10 \
29 |   handler.run \
30 |   "{\"channel\": \"$CHANNEL\"}"
31 | 


--------------------------------------------------------------------------------
/packages/lambda/src/flags.js:
--------------------------------------------------------------------------------
 1 | const LOGGING_FLAGS = process.env.DEBUG
 2 |   ? ['--enable-logging', '--log-level=0']
 3 |   : []
 4 | 
 5 | export default [
 6 |   ...LOGGING_FLAGS,
 7 |   '--disable-dev-shm-usage', // disable /dev/shm tmpfs usage on Lambda
 8 | 
 9 |   // @TODO: review if these are still relevant:
10 |   '--disable-gpu',
11 |   '--single-process', // Currently wont work without this :-(
12 | 
13 |   // https://groups.google.com/a/chromium.org/d/msg/headless-dev/qqbZVZ2IwEw/Y95wJUh2AAAJ
14 |   '--no-zygote', // helps avoid zombies
15 | 
16 |   '--no-sandbox',
17 | ]
18 | 


--------------------------------------------------------------------------------
/packages/lambda/src/index.js:
--------------------------------------------------------------------------------
  1 | import fs from 'fs'
  2 | // import path from 'path'
  3 | import LambdaChromeLauncher from './launcher'
  4 | import { debug, processExists } from './utils'
  5 | import DEFAULT_CHROME_FLAGS from './flags'
  6 | 
  7 | const DEVTOOLS_PORT = 9222
  8 | const DEVTOOLS_HOST = 'http://127.0.0.1'
  9 | 
 10 | // Prepend NSS related libraries and binaries to the library path and path respectively on lambda.
 11 | /* if (process.env.AWS_EXECUTION_ENV) {
 12 |   const nssSubPath = fs.readFileSync(path.join(__dirname, 'nss', 'latest'), 'utf8').trim();
 13 |   const nssPath = path.join(__dirname, 'nss', subnssSubPathPath);
 14 | 
 15 |   process.env.LD_LIBRARY_PATH = path.join(nssPath, 'lib') +  ':' + process.env.LD_LIBRARY_PATH;
 16 |   process.env.PATH = path.join(nssPath, 'bin') + ':' + process.env.PATH;
 17 | } */
 18 | 
 19 | // persist the instance across invocations
 20 | // when the *lambda* container is reused.
 21 | let chromeInstance
 22 | 
 23 | export default async function launch ({
 24 |   flags = [],
 25 |   chromePath,
 26 |   port = DEVTOOLS_PORT,
 27 |   forceLambdaLauncher = false,
 28 | } = {}) {
 29 |   const chromeFlags = [...DEFAULT_CHROME_FLAGS, ...flags]
 30 | 
 31 |   if (!chromeInstance || !processExists(chromeInstance.pid)) {
 32 |     if (process.env.AWS_EXECUTION_ENV || forceLambdaLauncher) {
 33 |       chromeInstance = new LambdaChromeLauncher({
 34 |         chromePath,
 35 |         chromeFlags,
 36 |         port,
 37 |       })
 38 |     } else {
 39 |       // This let's us use chrome-launcher in local development,
 40 |       // but omit it from the lambda function's zip artefact
 41 |       try {
 42 |         // eslint-disable-next-line
 43 |         const { Launcher: LocalChromeLauncher } = require('chrome-launcher')
 44 |         chromeInstance = new LocalChromeLauncher({
 45 |           chromePath,
 46 |           chromeFlags: flags,
 47 |           port,
 48 |         })
 49 |       } catch (error) {
 50 |         throw new Error('@serverless-chrome/lambda: Unable to find "chrome-launcher". ' +
 51 |             "Make sure it's installed if you wish to develop locally.")
 52 |       }
 53 |     }
 54 |   }
 55 | 
 56 |   debug('Spawning headless shell')
 57 | 
 58 |   const launchStartTime = Date.now()
 59 | 
 60 |   try {
 61 |     await chromeInstance.launch()
 62 |   } catch (error) {
 63 |     debug('Error trying to spawn chrome:', error)
 64 | 
 65 |     if (process.env.DEBUG) {
 66 |       debug(
 67 |         'stdout log:',
 68 |         fs.readFileSync(`${chromeInstance.userDataDir}/chrome-out.log`, 'utf8')
 69 |       )
 70 |       debug(
 71 |         'stderr log:',
 72 |         fs.readFileSync(`${chromeInstance.userDataDir}/chrome-err.log`, 'utf8')
 73 |       )
 74 |     }
 75 | 
 76 |     throw new Error('Unable to start Chrome. If you have the DEBUG env variable set,' +
 77 |         'there will be more in the logs.')
 78 |   }
 79 | 
 80 |   const launchTime = Date.now() - launchStartTime
 81 | 
 82 |   debug(`It took ${launchTime}ms to spawn chrome.`)
 83 | 
 84 |   // unref the chrome instance, otherwise the lambda process won't end correctly
 85 |   /* @TODO: make this an option?
 86 |     There's an option to change callbackWaitsForEmptyEventLoop in the Lambda context
 87 |     http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
 88 |     Which means you could log chrome output to cloudwatch directly
 89 |     without unreffing chrome.
 90 |   */
 91 |   if (chromeInstance.chrome) {
 92 |     chromeInstance.chrome.removeAllListeners()
 93 |     chromeInstance.chrome.unref()
 94 |   }
 95 | 
 96 |   return {
 97 |     pid: chromeInstance.pid,
 98 |     port: chromeInstance.port,
 99 |     url: `${DEVTOOLS_HOST}:${chromeInstance.port}`,
100 |     log: `${chromeInstance.userDataDir}/chrome-out.log`,
101 |     errorLog: `${chromeInstance.userDataDir}/chrome-err.log`,
102 |     pidFile: `${chromeInstance.userDataDir}/chrome.pid`,
103 |     metaData: {
104 |       launchTime,
105 |       didLaunch: !!chromeInstance.pid,
106 |     },
107 |     async kill () {
108 |       // Defer killing chrome process to the end of the execution stack
109 |       // so that the node process doesn't end before chrome exists,
110 |       // avoiding chrome becoming orphaned.
111 |       setTimeout(async () => {
112 |         chromeInstance.kill()
113 |         chromeInstance = undefined
114 |       }, 0)
115 |     },
116 |   }
117 | }
118 | 


--------------------------------------------------------------------------------
/packages/lambda/src/index.test.js:
--------------------------------------------------------------------------------
 1 | import test from 'ava'
 2 | import * as chromeFinder from 'chrome-launcher/dist/chrome-finder'
 3 | import launch from './index'
 4 | 
 5 | const DEFAULT_TEST_FLAGS = ['--headless']
 6 | 
 7 | async function getLocalChromePath () {
 8 |   const installations = await chromeFinder[process.platform]()
 9 | 
10 |   if (installations.length === 0) {
11 |     throw new Error('No Chrome Installations Found')
12 |   }
13 | 
14 |   return installations[0]
15 | }
16 | 
17 | test.serial('Chrome should launch using LocalChromeLauncher', async (t) => {
18 |   const chromePath = await getLocalChromePath()
19 |   const chrome = launch({
20 |     flags: DEFAULT_TEST_FLAGS,
21 |     chromePath,
22 |     port: 9220,
23 |   })
24 | 
25 |   t.notThrows(chrome)
26 | 
27 |   const instance = await chrome
28 | 
29 |   t.truthy(instance.pid, 'pid should be set')
30 |   t.truthy(instance.port, 'port should be set')
31 |   t.is(instance.port, 9220, 'port should be 9220')
32 | 
33 |   instance.kill()
34 | })
35 | 
36 | // Covered by the integration-test.
37 | test('Chrome should launch using LambdaChromeLauncher', (t) => {
38 |   // @TODO: proper test..
39 |   t.pass()
40 | })
41 | 


--------------------------------------------------------------------------------
/packages/lambda/src/launcher.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |   Large portion of this is inspired by/taken from Lighthouse/chrome-launcher.
  3 |   It is Copyright Google Inc, licensed under Apache License, Version 2.0.
  4 |   https://github.com/GoogleChrome/lighthouse/blob/master/chrome-launcher/chrome-launcher.ts
  5 | 
  6 |   We ship a modified version because the original verion comes with too
  7 |   many dependencies which complicates packaging of serverless services.
  8 | */
  9 | 
 10 | import path from 'path'
 11 | import fs from 'fs'
 12 | import { execSync, spawn } from 'child_process'
 13 | import net from 'net'
 14 | import http from 'http'
 15 | import { delay, debug, makeTempDir, clearConnection } from './utils'
 16 | import DEFAULT_CHROME_FLAGS from './flags'
 17 | 
 18 | const CHROME_PATH = path.resolve(__dirname, './headless-chromium')
 19 | 
 20 | export default class Launcher {
 21 |   constructor (options = {}) {
 22 |     const {
 23 |       chromePath = CHROME_PATH,
 24 |       chromeFlags = [],
 25 |       startingUrl = 'about:blank',
 26 |       port = 0,
 27 |     } = options
 28 | 
 29 |     this.tmpDirandPidFileReady = false
 30 |     this.pollInterval = 500
 31 |     this.pidFile = ''
 32 |     this.startingUrl = 'about:blank'
 33 |     this.outFile = null
 34 |     this.errFile = null
 35 |     this.chromePath = CHROME_PATH
 36 |     this.chromeFlags = []
 37 |     this.requestedPort = 0
 38 |     this.userDataDir = ''
 39 |     this.port = 9222
 40 |     this.pid = null
 41 |     this.chrome = undefined
 42 | 
 43 |     this.options = options
 44 |     this.startingUrl = startingUrl
 45 |     this.chromeFlags = chromeFlags
 46 |     this.chromePath = chromePath
 47 |     this.requestedPort = port
 48 |   }
 49 | 
 50 |   get flags () {
 51 |     return [
 52 |       ...DEFAULT_CHROME_FLAGS,
 53 |       `--remote-debugging-port=${this.port}`,
 54 |       `--user-data-dir=${this.userDataDir}`,
 55 |       '--disable-setuid-sandbox',
 56 |       ...this.chromeFlags,
 57 |       this.startingUrl,
 58 |     ]
 59 |   }
 60 | 
 61 |   prepare () {
 62 |     this.userDataDir = this.options.userDataDir || makeTempDir()
 63 |     this.outFile = fs.openSync(`${this.userDataDir}/chrome-out.log`, 'a')
 64 |     this.errFile = fs.openSync(`${this.userDataDir}/chrome-err.log`, 'a')
 65 |     this.pidFile = '/tmp/chrome.pid'
 66 |     this.tmpDirandPidFileReady = true
 67 |   }
 68 | 
 69 |   // resolves if ready, rejects otherwise
 70 |   isReady () {
 71 |     return new Promise((resolve, reject) => {
 72 |       const client = net.createConnection(this.port)
 73 | 
 74 |       client.once('error', (error) => {
 75 |         clearConnection(client)
 76 |         reject(error)
 77 |       })
 78 | 
 79 |       client.once('connect', () => {
 80 |         clearConnection(client)
 81 |         resolve()
 82 |       })
 83 |     })
 84 |   }
 85 | 
 86 |   // resolves when debugger is ready, rejects after 10 polls
 87 |   waitUntilReady () {
 88 |     const launcher = this
 89 | 
 90 |     return new Promise((resolve, reject) => {
 91 |       let retries = 0;
 92 |       (function poll () {
 93 |         debug('Waiting for Chrome', retries)
 94 | 
 95 |         launcher
 96 |           .isReady()
 97 |           .then(() => {
 98 |             debug('Started Chrome')
 99 |             resolve()
100 |           })
101 |           .catch((error) => {
102 |             retries += 1
103 | 
104 |             if (retries > 10) {
105 |               return reject(error)
106 |             }
107 | 
108 |             return delay(launcher.pollInterval).then(poll)
109 |           })
110 |       }())
111 |     })
112 |   }
113 | 
114 |   // resolves when chrome is killed, rejects  after 10 polls
115 |   waitUntilKilled () {
116 |     return Promise.all([
117 |       new Promise((resolve, reject) => {
118 |         let retries = 0
119 |         const server = http.createServer()
120 | 
121 |         server.once('listening', () => {
122 |           debug('Confirmed Chrome killed')
123 |           server.close(resolve)
124 |         })
125 | 
126 |         server.on('error', () => {
127 |           retries += 1
128 | 
129 |           debug('Waiting for Chrome to terminate..', retries)
130 | 
131 |           if (retries > 10) {
132 |             reject(new Error('Chrome is still running after 10 retries'))
133 |           }
134 | 
135 |           setTimeout(() => {
136 |             server.listen(this.port)
137 |           }, this.pollInterval)
138 |         })
139 | 
140 |         server.listen(this.port)
141 |       }),
142 |       new Promise((resolve) => {
143 |         this.chrome.on('close', resolve)
144 |       }),
145 |     ])
146 |   }
147 | 
148 |   async spawn () {
149 |     const spawnPromise = new Promise(async (resolve) => {
150 |       if (this.chrome) {
151 |         debug(`Chrome already running with pid ${this.chrome.pid}.`)
152 |         return resolve(this.chrome.pid)
153 |       }
154 | 
155 |       const chrome = spawn(this.chromePath, this.flags, {
156 |         detached: true,
157 |         stdio: ['ignore', this.outFile, this.errFile],
158 |       })
159 | 
160 |       this.chrome = chrome
161 | 
162 |       // unref the chrome instance, otherwise the lambda process won't end correctly
163 |       if (chrome.chrome) {
164 |         chrome.chrome.removeAllListeners()
165 |         chrome.chrome.unref()
166 |       }
167 | 
168 |       fs.writeFileSync(this.pidFile, chrome.pid.toString())
169 | 
170 |       debug(
171 |         'Launcher',
172 |         `Chrome running with pid ${chrome.pid} on port ${this.port}.`
173 |       )
174 | 
175 |       return resolve(chrome.pid)
176 |     })
177 | 
178 |     const pid = await spawnPromise
179 |     await this.waitUntilReady()
180 |     return pid
181 |   }
182 | 
183 |   async launch () {
184 |     if (this.requestedPort !== 0) {
185 |       this.port = this.requestedPort
186 | 
187 |       // If an explict port is passed first look for an open connection...
188 |       try {
189 |         return await this.isReady()
190 |       } catch (err) {
191 |         debug(
192 |           'ChromeLauncher',
193 |           `No debugging port found on port ${
194 |             this.port
195 |           }, launching a new Chrome.`
196 |         )
197 |       }
198 |     }
199 | 
200 |     if (!this.tmpDirandPidFileReady) {
201 |       this.prepare()
202 |     }
203 | 
204 |     this.pid = await this.spawn()
205 |     return Promise.resolve()
206 |   }
207 | 
208 |   kill () {
209 |     return new Promise(async (resolve, reject) => {
210 |       if (this.chrome) {
211 |         debug('Trying to terminate Chrome instance')
212 | 
213 |         try {
214 |           process.kill(-this.chrome.pid)
215 | 
216 |           debug('Waiting for Chrome to terminate..')
217 |           await this.waitUntilKilled()
218 |           debug('Chrome successfully terminated.')
219 | 
220 |           this.destroyTemp()
221 | 
222 |           delete this.chrome
223 |           return resolve()
224 |         } catch (error) {
225 |           debug('Chrome could not be killed', error)
226 |           return reject(error)
227 |         }
228 |       } else {
229 |         // fail silently as we did not start chrome
230 |         return resolve()
231 |       }
232 |     })
233 |   }
234 | 
235 |   destroyTemp () {
236 |     return new Promise((resolve) => {
237 |       // Only clean up the tmp dir if we created it.
238 |       if (
239 |         this.userDataDir === undefined ||
240 |         this.options.userDataDir !== undefined
241 |       ) {
242 |         return resolve()
243 |       }
244 | 
245 |       if (this.outFile) {
246 |         fs.closeSync(this.outFile)
247 |         delete this.outFile
248 |       }
249 | 
250 |       if (this.errFile) {
251 |         fs.closeSync(this.errFile)
252 |         delete this.errFile
253 |       }
254 | 
255 |       return execSync(`rm -Rf ${this.userDataDir}`, resolve)
256 |     })
257 |   }
258 | }
259 | 


--------------------------------------------------------------------------------
/packages/lambda/src/utils.js:
--------------------------------------------------------------------------------
 1 | import { execSync } from 'child_process'
 2 | 
 3 | export function clearConnection (client) {
 4 |   if (client) {
 5 |     client.removeAllListeners()
 6 |     client.end()
 7 |     client.destroy()
 8 |     client.unref()
 9 |   }
10 | }
11 | 
12 | export function debug (...args) {
13 |   return process.env.DEBUG
14 |     ? console.log('@serverless-chrome/lambda:', ...args)
15 |     : undefined
16 | }
17 | 
18 | export async function delay (time) {
19 |   return new Promise(resolve => setTimeout(resolve, time))
20 | }
21 | 
22 | export function makeTempDir () {
23 |   return execSync('mktemp -d -t chrome.XXXXXXX')
24 |     .toString()
25 |     .trim()
26 | }
27 | 
28 | /**
29 |  * Checks if a process currently exists by process id.
30 |  * @param pid number process id to check if exists
31 |  * @returns boolean true if process exists, false if otherwise
32 |  */
33 | export function processExists (pid) {
34 |   let exists = true
35 |   try {
36 |     process.kill(pid, 0)
37 |   } catch (error) {
38 |     exists = false
39 |   }
40 |   return exists
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/README.md:
--------------------------------------------------------------------------------
  1 | # Serverless-framework Headless Chrome Plugin
  2 | 
  3 | A [Serverless-framework](https://github.com/serverless/serverless) plugin which bundles the [@serverless-chrome/lambda](/packages/lambda) package and ensures that Headless Chrome is running when your function handler is invoked.
  4 | 
  5 | [![npm](https://img.shields.io/npm/v/serverless-plugin-chrome.svg?style=flat-square)](https://www.npmjs.com/package/serverless-plugin-chrome)
  6 | 
  7 | ## Contents
  8 | 1. [Installation](#installation)
  9 | 1. [Setup](#setup)
 10 | 1. [Examples](#examples)
 11 | 1. [Local Development](#local-development)
 12 | 1. [Configuration](#configuration)
 13 | 
 14 | 
 15 | ## Installation
 16 | 
 17 | Install with yarn:
 18 | 
 19 | ```bash
 20 | yarn add --dev serverless-plugin-chrome
 21 | ```
 22 | 
 23 | Install with npm:
 24 | 
 25 | ```bash
 26 | npm install --save-dev serverless-plugin-chrome
 27 | ```
 28 | 
 29 | Requires Node 6.10 runtime.
 30 | 
 31 | 
 32 | ## Setup
 33 | 
 34 | Add the following plugin to your `serverless.yml`:
 35 | 
 36 | ```yaml
 37 | plugins:
 38 |   - serverless-plugin-chrome
 39 | ```
 40 | 
 41 | Then, in your handler code.. Do whatever you want. Chrome will be running!
 42 | 
 43 | ```js
 44 | const CDP = require('chrome-remote-interface')
 45 | 
 46 | module.exports.hello = (event, context, callback, chrome) => {
 47 |   // Chrome is already running!
 48 | 
 49 |   CDP.Version()
 50 |     .then((versionInfo) => {
 51 |       callback(null, {
 52 |         statusCode: 200,
 53 |         body: JSON.stringify({
 54 |           versionInfo,
 55 |           chrome,
 56 |         }),
 57 |       })
 58 |     })
 59 |     .catch((error) => {
 60 |       callback(null, {
 61 |         statusCode: 500,
 62 |         body: JSON.stringify({
 63 |           error,
 64 |         }),
 65 |       })
 66 |     })
 67 | }
 68 | ```
 69 | 
 70 | Further details are available in the [Serverless Lambda example](/examples/serverless-framework/aws).
 71 | 
 72 | 
 73 | ## Examples
 74 | 
 75 | Example functions are available [here](/examples/serverless-framework). They include:
 76 | 
 77 | - Screenshot capturing handler: takes a picture of a URL
 78 | - print-to-PDF handler: turns a URL into a PDF
 79 | 
 80 | 
 81 | ## Local Development
 82 | 
 83 | Local development is supported. You must install the `chrome-launcher` package in your project. A locally installed version of Chrome will be launched.
 84 | 
 85 | **Command line flags (or "switches")**
 86 | 
 87 | The behavior of Chrome does vary between platforms. It may be necessary to experiment with flags to get the results you desire. On Lambda [default flags](/packages/lambda/src/flags.js) are used, but in development no default flags are used.
 88 | 
 89 | ## Configuration
 90 | 
 91 | You can pass custom flags with which to launch Chrome using the `custom` section in `serverless.yml`. For example:
 92 | 
 93 | ```yaml
 94 | plugins:
 95 |   - serverless-plugin-chrome
 96 | 
 97 | custom:
 98 |   chrome:
 99 |     flags:
100 |       - --window-size=1280,1696 # Letter size
101 |       - --hide-scrollbars
102 |       - --ignore-certificate-errors
103 |     functions:
104 |       - enableChromeOnThisFunctionName
105 |       - mySuperChromeFunction
106 | ```
107 | 
108 | It is also possible to enable Chrome on only specific functions in your service using the `custom.chrome.functions` configuration. For example:
109 | 
110 | ```yaml
111 | custom:
112 |   chrome:
113 |     functions:
114 |       - enableChromeOnThisFunctionName
115 |       - mySuperChromeFunction
116 | ```
117 | 
118 | You can enable debugging/logging output by specifying the DEBUG env variable in the provider section of `serverless.yml`. For example:
119 | 
120 | ```yaml
121 | provider:
122 |   name: aws
123 |   runtime: nodejs6.10
124 |   environment:
125 |     DEBUG: "*"
126 | 
127 | plugins:
128 |   - serverless-plugin-chrome
129 | ```
130 | 
131 | 
132 | ### Using with other plugins
133 | 
134 | Load order is important.
135 | 
136 | For example, if you're using the [serverless-webpack](https://github.com/serverless-heaven/serverless-webpack) plugin, your plugin section should be:
137 | 
138 | ```yaml
139 | plugins:
140 |   - serverless-plugin-chrome # 1st
141 |   - serverless-webpack
142 | ```
143 | 
144 | However, with the [serverless-plugin-typescript](https://github.com/graphcool/serverless-plugin-typescript) plugin, the order is:
145 | 
146 | ```yaml
147 | plugins:
148 |   - serverless-plugin-typescript
149 |   - serverless-plugin-chrome # 2nd
150 | ```
151 | 
152 | 
153 | ## Troubleshooting
154 | 
155 | <details id="ts-aws-client-timeout">
156 |   <summary>I keep getting a timeout error when deploying and it's really annoying.</summary>
157 | 
158 |   Indeed, that is annoying. I've had the same problem, and so that's why it's now here in this troubleshooting section. This may be an issue in the underlying AWS SDK when using a slower Internet connection. Try changing the `AWS_CLIENT_TIMEOUT` environment variable to a higher value. For example, in your command prompt enter the following and try deploying again:
159 | 
160 | ```bash
161 | export AWS_CLIENT_TIMEOUT=3000000
162 | ```
163 | </details>
164 | 
165 | <details id="ts-argh">
166 |   <summary>Aaaaaarggghhhhhh!!!</summary>
167 | 
168 |   Uuurrrggghhhhhh! Have you tried [filing an Issue](https://github.com/adieuadieu/serverless-chrome/issues/new)?
169 | </details>
170 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/integration-test/.serverless_plugins/serverless-plugin-chrome:
--------------------------------------------------------------------------------
1 | ../../


--------------------------------------------------------------------------------
/packages/serverless-plugin/integration-test/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "serverless-plugin-chrome-e2e",
 3 |   "version": "0.0.0",
 4 |   "private": true,
 5 |   "description": "",
 6 |   "main": "handler.js",
 7 |   "scripts": {
 8 |     "test": "echo \"Error: no test specified\" && exit 1",
 9 |     "deploy": "serverless deploy",
10 |     "invoke": "serverless invoke --function test"
11 |   },
12 |   "author": "Marco Lüthy",
13 |   "license": "MIT",
14 |   "dependencies": {
15 |     "chrome-remote-interface": "0.25.5"
16 |   },
17 |   "devDependencies": {
18 |     "@serverless-chrome/lambda": "1.0.0-44",
19 |     "serverless": "1.27.2",
20 |     "serverless-plugin-typescript": "1.1.5"
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/integration-test/serverless.yml:
--------------------------------------------------------------------------------
 1 | service: serverless-chrome-sls-plugin-test
 2 | 
 3 | provider:
 4 |   name: aws
 5 |   runtime: nodejs8.10
 6 |   stage: dev
 7 |   region: us-east-1
 8 |   environment:
 9 |     DEBUG: "*"
10 | 
11 | plugins:
12 | #  - serverless-plugin-typescript
13 |   - serverless-plugin-chrome
14 | 
15 | custom:
16 |   chrome:
17 |     flags:
18 |       - --window-size=1280,1696 # Letter size
19 |       - --hide-scrollbars
20 |       - --ignore-certificate-errors
21 |     functions:
22 |       - test
23 |       - anotherTest
24 | 
25 | functions:
26 |   test:
27 |     handler: src/handler.default
28 |   anotherTest:
29 |     handler: src/anotherHandler.default
30 |     package:
31 |       # individually: true
32 |   noChromeHere:
33 |     handler: src/noChrome.default
34 | 
35 | 
36 |   # typescript-test:
37 |   #   handler: src/typescript-handler.default
38 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/integration-test/src/anotherHandler.js:
--------------------------------------------------------------------------------
1 | const cdp = require('chrome-remote-interface')
2 | 
3 | module.exports.default = async (event, context, callback, chrome) => ({
4 |   versionInfo: await cdp.Version(),
5 |   chrome,
6 | })
7 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/integration-test/src/handler.js:
--------------------------------------------------------------------------------
1 | const cdp = require('chrome-remote-interface')
2 | 
3 | module.exports.default = async (event, context, callback, chrome) => ({
4 |   versionInfo: await cdp.Version(),
5 |   chrome,
6 | })
7 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/integration-test/src/noChrome.js:
--------------------------------------------------------------------------------
1 | module.exports.default = () => ({
2 |   message: 'No chrome here :-(',
3 | })
4 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/integration-test/src/typescript-handler.ts:
--------------------------------------------------------------------------------
1 | import * as cdp from 'chrome-remote-interface'
2 | 
3 | export default async function (event, context, callback, chrome) {
4 |   return {
5 |     versionInfo: await cdp.Version(),
6 |     chrome,
7 |   }
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "serverless-plugin-chrome",
 3 |   "version": "1.0.0-70",
 4 |   "description": "A Serverless-framework plugin that takes care of running headless Chrome so that you can move on with getting things done.",
 5 |   "keywords": [
 6 |     "serverless",
 7 |     "serverless-framework",
 8 |     "chrome",
 9 |     "chromium",
10 |     "headless",
11 |     "aws",
12 |     "lambda"
13 |   ],
14 |   "main": "dist/index.js",
15 |   "module": "dist/index.es.js",
16 |   "files": [
17 |     "dist",
18 |     "src",
19 |     "package.json",
20 |     "README.md"
21 |   ],
22 |   "scripts": {
23 |     "clean": "rm -Rf dist/",
24 |     "test": "npm run test:integration",
25 |     "test:integration": "scripts/test-integration.sh",
26 |     "watch:test": "ava --watch",
27 |     "build": "rollup -c",
28 |     "dev": "rollup -c -w",
29 |     "prepublishOnly": "npm run clean && npm run build",
30 |     "upgrade-dependencies": "yarn upgrade-interactive --latest --exact"
31 |   },
32 |   "repository": {
33 |     "type": "git",
34 |     "url": "https://github.com/adieuadieu/serverless-chrome.git"
35 |   },
36 |   "author": "Marco Lüthy",
37 |   "license": "MIT",
38 |   "bugs": {
39 |     "url": "https://github.com/adieuadieu/serverless-chrome/issues"
40 |   },
41 |   "homepage": "https://github.com/adieuadieu/serverless-chrome/tree/master/packages/serverless-plugin",
42 |   "dependencies": {
43 |     "@serverless-chrome/lambda": "1.0.0-70",
44 |     "fs-p": "2.0.0",
45 |     "globby": "6.1.0"
46 |   },
47 |   "devDependencies": {
48 |     "ava": "0.25.0",
49 |     "babel-core": "6.26.3",
50 |     "babel-plugin-transform-object-rest-spread": "6.26.0",
51 |     "babel-preset-env": "1.7.0",
52 |     "babel-register": "6.26.0",
53 |     "chrome-launcher": "0.10.2",
54 |     "rollup": "0.59.1",
55 |     "rollup-plugin-babel": "3.0.4",
56 |     "rollup-plugin-node-resolve": "3.3.0"
57 |   },
58 |   "peerDependences": {
59 |     "serverless": "^2.32.0"
60 |   },
61 |   "babel": {
62 |     "sourceMaps": true,
63 |     "presets": [
64 |       [
65 |         "env",
66 |         {
67 |           "modules": "commonjs",
68 |           "targets": {
69 |             "node": "6.10"
70 |           }
71 |         }
72 |       ]
73 |     ]
74 |   }
75 | }
76 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/rollup.config.js:
--------------------------------------------------------------------------------
 1 | import resolve from 'rollup-plugin-node-resolve'
 2 | // import commonjs from 'rollup-plugin-commonjs'
 3 | import babel from 'rollup-plugin-babel'
 4 | 
 5 | export default {
 6 |   input: 'src/index.js',
 7 |   output: [
 8 |     { file: 'dist/index.js', format: 'cjs', sourcemap: true },
 9 |     { file: 'dist/index.es.js', format: 'es', sourcemap: true },
10 |   ],
11 | 
12 |   plugins: [
13 |     resolve({
14 |       // module: true, // Default: true
15 |       // jsnext: true, // Default: false
16 |       // main: true, // Default: true
17 |       extensions: ['.js'], // Default: ['.js']
18 |       // Lock the module search in this path (like a chroot). Module defined
19 |       // outside this path will be mark has external
20 |       // jail: './', // Default: '/'
21 |       // If true, inspect resolved files to check that they are
22 |       // ES2015 modules
23 |       // modulesOnly: true, // Default: false
24 |     }),
25 |     // commonjs({}),
26 |     babel({
27 |       babelrc: false,
28 |       plugins: ['transform-object-rest-spread'],
29 |       presets: [
30 |         [
31 |           'env',
32 |           {
33 |             modules: false,
34 |             targets: {
35 |               node: '6.10',
36 |             },
37 |           },
38 |         ],
39 |       ],
40 |     }),
41 |   ],
42 |   external: ['path', 'globby', 'fs-p'],
43 | }
44 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/scripts/test-integration.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Runs a simple integration-test lambda handler in Docker
 6 | #
 7 | # Requires Docker, jq
 8 | #
 9 | # Usage: ./integration-test.sh
10 | #
11 | 
12 | set -e
13 | 
14 | cd "$(dirname "$0")/.."
15 | 
16 | # PACKAGE_DIRECTORY=$(pwd)
17 | TEST_DIRECTORY=".test"
18 | 
19 | npm run build
20 | 
21 | cd integration-test
22 | 
23 | ./node_modules/.bin/serverless package
24 | unzip -o -d "$TEST_DIRECTORY" .serverless/**.zip
25 | 
26 | CHROMIUM_VERSION=$(docker run \
27 |   -v "$PWD/$TEST_DIRECTORY":/var/task \
28 |   lambci/lambda:nodejs8.10 \
29 |   src/handler.default | \
30 |   jq -re '.versionInfo.Browser')
31 | 
32 | rm -Rf "$TEST_DIRECTORY"
33 | 
34 | echo "Chromium version $CHROMIUM_VERSION"
35 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/src/constants.js:
--------------------------------------------------------------------------------
 1 | export const SERVERLESS_FOLDER = '.serverless'
 2 | export const BUILD_FOLDER = '.build'
 3 | 
 4 | export const SUPPORTED_PROVIDERS = ['aws']
 5 | export const SUPPORTED_RUNTIMES = ['nodejs6.10', 'nodejs8.10']
 6 | 
 7 | export const INCLUDES = [
 8 |   'node_modules/@serverless-chrome/lambda/package.json',
 9 |   'node_modules/@serverless-chrome/lambda/dist/bundle.cjs.js',
10 |   'node_modules/@serverless-chrome/lambda/dist/headless-chromium',
11 | ]
12 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/src/index.js:
--------------------------------------------------------------------------------
  1 | /*
  2 | @TODO:
  3 |   - handle package.individually?
  4 |     https://github.com/serverless/serverless/blob/master/lib/plugins/package/lib/packageService.js#L37
  5 |   - support for enabling chrome only on specific functions?
  6 |   - instead of including fs-p dep, use the fs methods from the Utils class provided by Serverless
  7 |     or use fs-extra directly.
  8 |   - config option to, instead of including chrome in artifact zip, download it on
  9 |     cold-start invocations this could be useful for development, instead of having
 10 |     to upload 50MB each deploy
 11 |   - tests.
 12 |   - custom.chrome.functions breaks when a wrapped and non-wrapped function have the
 13 |     same handler.js file
 14 | */
 15 | 
 16 | import * as path from 'path'
 17 | import * as fs from 'fs-p' // deprecated. use fs-extra?
 18 | import globby from 'globby'
 19 | 
 20 | import { SERVERLESS_FOLDER, BUILD_FOLDER, INCLUDES } from './constants'
 21 | import {
 22 |   throwIfUnsupportedProvider,
 23 |   throwIfUnsupportedRuntime,
 24 |   throwIfWrongPluginOrder,
 25 |   getHandlerFileAndExportName,
 26 | } from './utils'
 27 | 
 28 | const wrapperTemplateMap = {
 29 |   'aws-nodejs6.10': 'wrapper-aws-nodejs.js',
 30 |   'aws-nodejs8.10': 'wrapper-aws-nodejs.js',
 31 | }
 32 | 
 33 | export default class ServerlessChrome {
 34 |   constructor (serverless, options) {
 35 |     this.serverless = serverless
 36 |     this.options = options
 37 | 
 38 |     const {
 39 |       provider: { name: providerName, runtime },
 40 |       plugins,
 41 |     } = serverless.service
 42 | 
 43 |     throwIfUnsupportedProvider(providerName)
 44 |     throwIfUnsupportedRuntime(runtime)
 45 |     throwIfWrongPluginOrder(plugins)
 46 | 
 47 |     this.hooks = {
 48 |       'before:offline:start:init': this.beforeCreateDeploymentArtifacts.bind(this),
 49 |       'before:package:createDeploymentArtifacts': this.beforeCreateDeploymentArtifacts.bind(this),
 50 |       'after:package:createDeploymentArtifacts': this.afterCreateDeploymentArtifacts.bind(this),
 51 |       'before:invoke:local:invoke': this.beforeCreateDeploymentArtifacts.bind(this),
 52 |       'after:invoke:local:invoke': this.cleanup.bind(this),
 53 | 
 54 |       'before:webpack:package:packExternalModules': this.webpackPackageBinaries.bind(this),
 55 |     }
 56 | 
 57 |     // only mess with the service path if we're not already known to be within a .build folder
 58 |     this.messWithServicePath = !plugins.includes('serverless-plugin-typescript')
 59 | 
 60 |     // annoyingly we have to do stuff differently if using serverless-webpack plugin. lame.
 61 |     this.webpack = plugins.includes('serverless-webpack')
 62 |   }
 63 | 
 64 |   async webpackPackageBinaries () {
 65 |     const { config: { servicePath }, service } = this.serverless
 66 |     const packagedIdividually = service.package && service.package.individually
 67 | 
 68 |     if (packagedIdividually) {
 69 |       const functionsToCopyTo =
 70 |         (service.custom && service.custom.chrome && service.custom.chrome.functions) ||
 71 |         service.getAllFunctions()
 72 | 
 73 |       await Promise.all(functionsToCopyTo.map(async (functionName) => {
 74 |         await fs.copy(
 75 |           path.join(servicePath, 'node_modules/@serverless-chrome/lambda/dist/headless-chromium'),
 76 |           path.resolve(servicePath, `.webpack/${functionName}/headless-chromium`)
 77 |         )
 78 |       }))
 79 |     } else {
 80 |       await fs.copy(
 81 |         path.join(servicePath, 'node_modules/@serverless-chrome/lambda/dist/headless-chromium'),
 82 |         path.resolve(servicePath, '.webpack/service/headless-chromium')
 83 |       )
 84 |     }
 85 |   }
 86 | 
 87 |   async beforeCreateDeploymentArtifacts () {
 88 |     const {
 89 |       config,
 90 |       cli,
 91 |       utils,
 92 |       service,
 93 |       service: {
 94 |         provider: { name: providerName, runtime },
 95 |       },
 96 |     } = this.serverless
 97 | 
 98 |     const functionsToWrap =
 99 |       (service.custom &&
100 |         service.custom.chrome &&
101 |         service.custom.chrome.functions) ||
102 |       service.getAllFunctions()
103 | 
104 |     service.package.include = service.package.include || []
105 |     service.package.patterns = service.package.patterns || []
106 | 
107 |     cli.log('Injecting Headless Chrome...')
108 | 
109 |     // Save original service path and functions
110 |     this.originalServicePath = config.servicePath
111 | 
112 |     // Fake service path so that serverless will know what to zip
113 |     // Unless, we're already in a .build folder from another plugin
114 |     if (this.messWithServicePath) {
115 |       config.servicePath = path.join(this.originalServicePath, BUILD_FOLDER)
116 | 
117 |       if (!fs.existsSync(config.servicePath)) {
118 |         fs.mkdirpSync(config.servicePath)
119 |       }
120 | 
121 |       // include node_modules into build
122 |       if (
123 |         !fs.existsSync(path.resolve(path.join(BUILD_FOLDER, 'node_modules')))
124 |       ) {
125 |         fs.symlinkSync(
126 |           path.resolve('node_modules'),
127 |           path.resolve(path.join(BUILD_FOLDER, 'node_modules')),
128 |           'junction'
129 |         )
130 |       }
131 | 
132 |       // include any "extras" from the "include" section
133 |       const files = await globby(
134 |         [...service.package.include, ...service.package.patterns, '**', '!node_modules/**'],
135 |         {
136 |           cwd: this.originalServicePath,
137 |         }
138 |       )
139 | 
140 |       files.forEach((filename) => {
141 |         const sourceFile = path.resolve(path.join(this.originalServicePath, filename))
142 |         const destFileName = path.resolve(path.join(config.servicePath, filename))
143 | 
144 |         const dirname = path.dirname(destFileName)
145 | 
146 |         if (!fs.existsSync(dirname)) {
147 |           fs.mkdirpSync(dirname)
148 |         }
149 | 
150 |         if (!fs.existsSync(destFileName)) {
151 |           fs.copySync(sourceFile, destFileName)
152 |         }
153 |       })
154 |     }
155 | 
156 |     // Add our node_modules dependencies to the package includes
157 |     service.package.patterns = [...service.package.patterns, ...INCLUDES]
158 | 
159 |     await Promise.all(functionsToWrap.map(async (functionName) => {
160 |       const { handler } = service.getFunction(functionName)
161 |       const { filePath, fileName, exportName } = getHandlerFileAndExportName(handler)
162 |       const handlerCodePath = path.join(config.servicePath, filePath)
163 | 
164 |       const originalFileRenamed = `${utils.generateShortId()}___${fileName}`
165 | 
166 |       const customPluginOptions =
167 |           (service.custom && service.custom.chrome) || {}
168 | 
169 |       const launcherOptions = {
170 |         ...customPluginOptions,
171 |         flags: customPluginOptions.flags || [],
172 |         chromePath: this.webpack ? '/var/task/headless-chromium' : undefined,
173 |       }
174 | 
175 |       // Read in the wrapper handler code template
176 |       const wrapperTemplate = await utils.readFile(path.resolve(
177 |         __dirname,
178 |         '..',
179 |         'src',
180 |         wrapperTemplateMap[`${providerName}-${runtime}`]
181 |       ))
182 | 
183 |       // Include the original handler via require
184 |       const wrapperCode = wrapperTemplate
185 |         .replace(
186 |           "'REPLACE_WITH_HANDLER_REQUIRE'",
187 |           `require('./${originalFileRenamed}')`
188 |         )
189 |         .replace("'REPLACE_WITH_OPTIONS'", JSON.stringify(launcherOptions))
190 |         .replace(/REPLACE_WITH_EXPORT_NAME/gm, exportName)
191 | 
192 |         // Move the original handler's file aside
193 |       await fs.move(
194 |         path.resolve(handlerCodePath, fileName),
195 |         path.resolve(handlerCodePath, originalFileRenamed)
196 |       )
197 | 
198 |       // Write the wrapper code to the function's handler path
199 |       await utils.writeFile(
200 |         path.resolve(handlerCodePath, fileName),
201 |         wrapperCode
202 |       )
203 |     }))
204 |   }
205 | 
206 |   async afterCreateDeploymentArtifacts () {
207 |     if (this.messWithServicePath) {
208 |       // Copy .build to .serverless
209 |       await fs.copy(
210 |         path.join(this.originalServicePath, BUILD_FOLDER, SERVERLESS_FOLDER),
211 |         path.join(this.originalServicePath, SERVERLESS_FOLDER)
212 |       )
213 | 
214 |       // this.serverless.service.package.artifact = path.join(
215 |       //   this.originalServicePath,
216 |       //   SERVERLESS_FOLDER
217 |       //   path.basename(this.serverless.service.package.artifact)
218 |       // )
219 | 
220 |       // Cleanup after everything is copied
221 |       await this.cleanup()
222 |     }
223 |   }
224 | 
225 |   async cleanup () {
226 |     // Restore service path
227 |     this.serverless.config.servicePath = this.originalServicePath
228 | 
229 |     // Remove temp build folder
230 |     fs.removeSync(path.join(this.originalServicePath, BUILD_FOLDER))
231 |   }
232 | }
233 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/src/utils.js:
--------------------------------------------------------------------------------
 1 | import * as path from 'path'
 2 | import { SUPPORTED_PROVIDERS, SUPPORTED_RUNTIMES } from './constants'
 3 | 
 4 | export function throwIfUnsupportedProvider (provider) {
 5 |   if (!SUPPORTED_PROVIDERS.includes(provider)) {
 6 |     throw new Error('The "serverless-plugin-chrome" plugin currently only supports AWS Lambda. ' +
 7 |         `Your service is using the "${provider}" provider.`)
 8 |   }
 9 | }
10 | 
11 | export function throwIfUnsupportedRuntime (runtime) {
12 |   if (!SUPPORTED_RUNTIMES.includes(runtime)) {
13 |     throw new Error('The "serverless-plugin-chrome" plugin only supports the Node.js 6.10 or 8.10 runtimes. ' +
14 |         `Your service is using the "${runtime}" provider.`)
15 |   }
16 | }
17 | 
18 | export function throwIfWrongPluginOrder (plugins) {
19 |   const comesBefore = ['serverless-plugin-typescript']
20 |   const comesAfter = ['serverless-webpack']
21 | 
22 |   const ourIndex = plugins.indexOf('serverless-plugin-chrome')
23 | 
24 |   plugins.forEach((plugin, index) => {
25 |     if (comesBefore.includes(plugin) && ourIndex < index) {
26 |       throw new Error(`The plugin "${plugin}" should appear before the "serverless-plugin-chrome"` +
27 |           ' plugin in the plugin configuration section of serverless.yml.')
28 |     }
29 | 
30 |     if (comesAfter.includes(plugin) && ourIndex > index) {
31 |       throw new Error(`The plugin "${plugin}" should appear after the "serverless-plugin-chrome"` +
32 |           ' plugin in the plugin configuration section of serverless.yml.')
33 |     }
34 |   })
35 | }
36 | 
37 | export function getHandlerFileAndExportName (handler = '') {
38 |   const fileParts = handler.split('.')
39 |   const exportName = fileParts.pop()
40 |   const file = fileParts.join('.')
41 | 
42 |   return {
43 |     filePath: path.dirname(file),
44 |     fileName: `${path.basename(file)}.js`, // is it OK to assume .js?
45 |     exportName,
46 |   }
47 | }
48 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/src/utils.test.js:
--------------------------------------------------------------------------------
 1 | import test from 'ava'
 2 | import {
 3 |   throwIfUnsupportedProvider,
 4 |   throwIfUnsupportedRuntime,
 5 |   throwIfWrongPluginOrder,
 6 |   getHandlerFileAndExportName,
 7 | } from './utils'
 8 | 
 9 | test('throwIfUnsupportedProvider()', (t) => {
10 |   t.throws(() => throwIfUnsupportedProvider('bogus'), Error)
11 |   t.notThrows(() => throwIfUnsupportedProvider('aws'))
12 | })
13 | 
14 | test('throwIfUnsupportedRuntime()', (t) => {
15 |   t.throws(() => throwIfUnsupportedRuntime('bogus'), Error)
16 |   t.notThrows(() => throwIfUnsupportedRuntime('nodejs6.10'))
17 | })
18 | 
19 | test('throwIfWrongPluginOrder()', (t) => {
20 |   t.throws(
21 |     () => throwIfWrongPluginOrder(['serverless-plugin-chrome', 'serverless-plugin-typescript']),
22 |     Error,
23 |     'Should throw when our plugin comes before the "serverless-plugin-typescript" plugin.'
24 |   )
25 | 
26 |   t.notThrows(
27 |     () => throwIfWrongPluginOrder(['serverless-plugin-typescript', 'serverless-plugin-chrome']),
28 |     'Should not throw when our plugin comes after the "serverless-plugin-typescript" plugin.'
29 |   )
30 | 
31 |   t.notThrows(
32 |     () => throwIfWrongPluginOrder(['serverless-plugin-chrome']),
33 |     'Should not throw when only our plugin is used.'
34 |   )
35 | 
36 |   t.throws(
37 |     () =>
38 |       throwIfWrongPluginOrder([
39 |         'serverless-webpack',
40 |         'bogus',
41 |         'serverless-plugin-chrome',
42 |         'serverless-plugin-typescript',
43 |       ]),
44 |     Error,
45 |     'Should throw when plugins are in order known to not work.'
46 |   )
47 | 
48 |   t.notThrows(
49 |     () =>
50 |       throwIfWrongPluginOrder([
51 |         'bogus',
52 |         'serverless-plugin-typescript',
53 |         'serverless-plugin-chrome',
54 |         'bogus',
55 |         'serverless-webpack',
56 |       ]),
57 |     'Should not throw when plugins are in order known to work.'
58 |   )
59 | })
60 | 
61 | test('getHandlerFileAndExportName()', (t) => {
62 |   const { filePath, fileName, exportName } = getHandlerFileAndExportName('nested/test/handler.foobar.test')
63 | 
64 |   t.is(filePath, 'nested/test')
65 |   t.is(fileName, 'handler.foobar.js')
66 |   t.is(exportName, 'test')
67 | })
68 | 


--------------------------------------------------------------------------------
/packages/serverless-plugin/src/wrapper-aws-nodejs.js:
--------------------------------------------------------------------------------
 1 | const launch = require('@serverless-chrome/lambda')
 2 | 
 3 | const handler = 'REPLACE_WITH_HANDLER_REQUIRE'
 4 | const options = 'REPLACE_WITH_OPTIONS'
 5 | 
 6 | module.exports.REPLACE_WITH_EXPORT_NAME = function ensureHeadlessChrome (
 7 |   event,
 8 |   context,
 9 |   callback
10 | ) {
11 |   return (typeof launch === 'function' ? launch : launch.default)(options)
12 |     .then(instance =>
13 |       handler.REPLACE_WITH_EXPORT_NAME(event, context, callback, instance))
14 |     .catch((error) => {
15 |       console.error(
16 |         'Error occured in serverless-plugin-chrome wrapper when trying to ' +
17 |           'ensure Chrome for REPLACE_WITH_EXPORT_NAME() handler.',
18 |         options,
19 |         error
20 |       )
21 | 
22 |       callback(error)
23 |     })
24 | }
25 | 


--------------------------------------------------------------------------------
/scripts/ci-daily.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Builds docker images
 6 | #
 7 | # Usage: ./ci-daily.sh stable|beta|dev [chromium|firefox]
 8 | #
 9 | 
10 | set -e
11 | 
12 | cd "$(dirname "$0")/.."
13 | 
14 | PROJECT_DIRECTORY=$(pwd)
15 | PACKAGE_DIRECTORY="$PROJECT_DIRECTORY/packages/lambda"
16 | 
17 | CHANNEL=${1:-stable}
18 | 
19 | if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
20 |   echo "Missing required AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY environment variables"
21 |   exit 1
22 | fi
23 | 
24 | if [ -z "$AWS_IAM_INSTANCE_ARN" ]; then
25 |   echo "Missing required AWS_IAM_INSTANCE_ARN environment variables"
26 |   exit 1
27 | fi
28 | 
29 | launch_if_new() {
30 |   BUILD_NAME=$1
31 | 
32 |   cd "$PACKAGE_DIRECTORY/builds/$BUILD_NAME"
33 | 
34 |   LATEST_VERSION=$(./latest.sh "$CHANNEL")
35 |   DOCKER_IMAGE=headless-$BUILD_NAME-for-aws-lambda
36 | 
37 |   if "$PROJECT_DIRECTORY/scripts/docker-image-exists.sh" "adieuadieu/$DOCKER_IMAGE" "$LATEST_VERSION"; then
38 |     echo "$BUILD_NAME version $LATEST_VERSION was previously built. Skipping build."
39 |   else
40 |     "$PROJECT_DIRECTORY/scripts/ec2-build.sh" "$BUILD_NAME" "$CHANNEL" "$LATEST_VERSION"
41 |   fi
42 | }
43 | 
44 | # main script
45 | 
46 | cd "$PROJECT_DIRECTORY"
47 | 
48 | if [ ! -z "$2" ]; then
49 |   launch_if_new "$2"
50 | else
51 |   cd "$PACKAGE_DIRECTORY/builds"
52 | 
53 |   for DOCKER_FILE in */Dockerfile; do
54 |     launch_if_new "${DOCKER_FILE%%/*}"
55 |   done
56 | fi
57 | 


--------------------------------------------------------------------------------
/scripts/docker-build-image.sh:
--------------------------------------------------------------------------------
  1 | #!/bin/sh
  2 | # shellcheck shell=dash
  3 | 
  4 | #
  5 | # Builds docker images
  6 | #
  7 | # Requires Docker, jq, and curl
  8 | #
  9 | # Usage: ./docker-build-image.sh stable|beta|dev [chromium|firefox] [version|git-tag]
 10 | #
 11 | 
 12 | set -e
 13 | 
 14 | cd "$(dirname "$0")/.."
 15 | 
 16 | CHANNEL=${1:-stable}
 17 | BROWSER=${2:-}
 18 | VERSION=${3:-}
 19 | 
 20 | DOCKER_ORG=${DOCKER_ORG:-adieuadieu}
 21 | 
 22 | PROJECT_DIRECTORY=$(pwd)
 23 | PACKAGE_DIRECTORY="$PROJECT_DIRECTORY/packages/lambda"
 24 | 
 25 | if [ -z "$1" ]; then
 26 |   echo "Missing required channel argument"
 27 |   exit 1
 28 | fi
 29 | 
 30 | build() {
 31 |   BUILD_NAME=$1
 32 |   DOCKER_IMAGE=headless-$BUILD_NAME-for-aws-lambda
 33 | 
 34 |   if [ -z "$VERSION" ]; then
 35 |     VERSION=$(./latest.sh "$CHANNEL")
 36 |   fi
 37 | 
 38 |   cd "$PACKAGE_DIRECTORY/builds/$BUILD_NAME"
 39 |     
 40 |   if "$PROJECT_DIRECTORY/scripts/docker-image-exists.sh" \
 41 |       "$DOCKER_ORG/$DOCKER_IMAGE" "$VERSION" \
 42 |     && [ -z "$FORCE_NEW_BUILD" ]; then
 43 |     echo "$BUILD_NAME version $VERSION was previously built. Skipping build."
 44 |   else
 45 |     echo "Building Docker image $BUILD_NAME version $VERSION"
 46 | 
 47 |     # Build in Docker
 48 |     docker build \
 49 |       --compress \
 50 |       -t "$DOCKER_ORG/$DOCKER_IMAGE-build:$VERSION" \
 51 |       --build-arg VERSION="$VERSION" \
 52 |       "build"
 53 | 
 54 |     mkdir -p dist/
 55 | 
 56 |     # Run the container
 57 |     docker run -dt --rm \
 58 |       --name "$DOCKER_IMAGE-build" \
 59 |       -p 9222:9222 \
 60 |       "$DOCKER_ORG/$DOCKER_IMAGE-build:$VERSION"
 61 | 
 62 |     # Give the container and browser some time to start up
 63 |     sleep 10
 64 | 
 65 |     # Test the build and return if it doesn't run
 66 |     if ! curl -fs http://localhost:9222/json/version; then
 67 |       echo "Unable to correctly run and connect to build via Docker."
 68 | 
 69 |       # @TODO: this is specific to chromium......
 70 |       echo "Here's some output:"
 71 | 
 72 |       docker logs "$DOCKER_IMAGE-build"
 73 | 
 74 |       docker run --init --rm \
 75 |         --entrypoint="/bin/headless-chromium" \
 76 |         "$DOCKER_ORG/$DOCKER_IMAGE-build:$VERSION" \
 77 |         --no-sandbox --disable-gpu http://google.com/
 78 |       return
 79 |     fi
 80 | 
 81 |     # Extract the binary produced in the build
 82 |     docker cp "$DOCKER_IMAGE-build:/bin/headless-$BUILD_NAME" dist/
 83 |     docker stop "$DOCKER_IMAGE-build"
 84 | 
 85 |     # Create the public Docker image
 86 |     # We do this because the image in which be build ends up being huge
 87 |     # due to the source code and build dependencies
 88 |     docker build \
 89 |       --compress \
 90 |       -t "$DOCKER_ORG/$DOCKER_IMAGE:$VERSION" \
 91 |       --build-arg VERSION="$VERSION" \
 92 |       "."
 93 | 
 94 |     if [ -n "$DO_PUSH" ]; then
 95 |       echo "Pushing image to Docker hub"
 96 | 
 97 |       # Only tag stable channel as latest
 98 |       if [ "$CHANNEL" = "stable" ]; then
 99 |         docker tag \
100 |           "$DOCKER_ORG/$DOCKER_IMAGE:$VERSION" \
101 |           "$DOCKER_ORG/$DOCKER_IMAGE:latest"
102 |       fi
103 | 
104 |       docker tag \
105 |         "$DOCKER_ORG/$DOCKER_IMAGE:$VERSION" \
106 |         "$DOCKER_ORG/$DOCKER_IMAGE:$CHANNEL"
107 | 
108 |       docker push "$DOCKER_ORG/$DOCKER_IMAGE"
109 |     fi
110 | 
111 |     #
112 |     # Upload a zipped binary to S3 if S3_BUCKET is set
113 |     # Prints a presigned S3 URL to the zip file
114 |     #
115 |     if [ -n "$S3_BUCKET" ]; then
116 |       ZIPFILE="headless-$BUILD_NAME-$VERSION-amazonlinux-2017-03.zip"
117 |       S3_OBJECT_URI="s3://$S3_BUCKET/$BUILD_NAME/$CHANNEL/$ZIPFILE"
118 | 
119 |       (
120 |         cd dist
121 |         zip -9 -D "$ZIPFILE" "headless-$BUILD_NAME"
122 |       )
123 | 
124 |       aws s3 \
125 |         cp "dist/$ZIPFILE" \
126 |         "$S3_OBJECT_URI" \
127 |         --region "$AWS_REGION"
128 | 
129 |       S3_PRESIGNED_URL=$(aws s3 presign \
130 |         "$S3_OBJECT_URI" \
131 |         --region "$AWS_REGION" \
132 |         --expires-in 86400 \
133 |       )
134 | 
135 |       printf "\n\nBinary archive URL: %s\n\n" "$S3_PRESIGNED_URL"
136 |     fi
137 |   fi
138 | }
139 | 
140 | # main script
141 | 
142 | cd "$PROJECT_DIRECTORY"
143 | 
144 | # Docker Login & enable docker push on successful login
145 | if scripts/docker-login.sh; then
146 |   DO_PUSH=1
147 | fi
148 | 
149 | if [ ! -z "$BROWSER" ]; then
150 |   build "$BROWSER"
151 | else
152 |   cd "$PACKAGE_DIRECTORY/builds"
153 | 
154 |   for DOCKER_FILE in */Dockerfile; do
155 |     build "${DOCKER_FILE%%/*}"
156 |   done
157 | fi
158 | 


--------------------------------------------------------------------------------
/scripts/docker-image-exists.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Check if a Docker image and tag exists on Docker Hub
 6 | #
 7 | # Requires curl
 8 | #
 9 | # Usage: ./docker-image-exists image [tag]
10 | #
11 | 
12 | set -e
13 | 
14 | # better implementation here: https://github.com/blueimp/shell-scripts/blob/master/bin/docker-image-exists.sh
15 | # ref: https://stackoverflow.com/a/39731444/845713
16 | 
17 | curl --silent -f -L "https://index.docker.io/v1/repositories/$1/tags/$2" > /dev/null
18 | 


--------------------------------------------------------------------------------
/scripts/docker-login.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Login to Docker Hub checking for credentials before doing so.
 6 | #
 7 | # Usage: ./docker-login.sh
 8 | #
 9 | 
10 | set -e
11 | 
12 | # Slightly naive and assumes we're not using multiple registries
13 | 
14 | if [ -f ~/.docker/config.json ] && \
15 |   [ "$(jq -re '.auths | length' ~/.docker/config.json)" -gt 0 ]; then
16 |   echo "Already logged in to Docker Hub"
17 |   exit 0
18 | fi
19 | 
20 | if [ -z "$DOCKER_USER" ] || [ -z "$DOCKER_PASS" ]; then
21 |   echo "Missing required DOCKER_USER and/or DOCKER_PASS environment variables"
22 |   exit 1
23 | fi  
24 | 
25 | # Log in to Docker Hub
26 | docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
27 | 


--------------------------------------------------------------------------------
/scripts/docker-pull-image.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Pull projects' latest Docker images
 6 | #
 7 | # Requires Docker
 8 | #
 9 | # Usage: ./docker-pull-image.sh [chromium|firefox] [base]
10 | #
11 | 
12 | set -e
13 | 
14 | cd "$(dirname "$0")/.."
15 | 
16 | PROJECT_DIRECTORY=$(pwd)
17 | PACKAGE_DIRECTORY="$PROJECT_DIRECTORY/packages/lambda"
18 | 
19 | # better implementation here: https://github.com/blueimp/shell-scripts/blob/master/bin/docker-image-exists.sh
20 | # ref: https://stackoverflow.com/a/39731444/845713
21 | docker_tag_exists() {
22 |   curl --silent -f -L "https://index.docker.io/v1/repositories/$1/tags/$2" > /dev/null
23 | }
24 | 
25 | build() {
26 |   BUILD_NAME=$1
27 |   DO_BASE_BUILD=$2
28 |   
29 |   cd "$PACKAGE_DIRECTORY/builds/$BUILD_NAME"
30 |   
31 |   LATEST_VERSION=$(./latest.sh)
32 |   
33 |   DOCKER_IMAGE=headless-$BUILD_NAME-for-aws-lambda
34 | 
35 |   if [ -n "$DO_BASE_BUILD" ]; then
36 |     DOCKER_IMAGE=$BUILD_NAME-for-amazonlinux-base
37 |   fi
38 | 
39 |   if docker_tag_exists "adieuadieu/$DOCKER_IMAGE" "$LATEST_VERSION"; then
40 |     echo "Pulling $BUILD_NAME version $LATEST_VERSION."
41 |     docker pull "adieuadieu/$DOCKER_IMAGE:$LATEST_VERSION"
42 |   else
43 |     echo "Docker image adieuadieu/$DOCKER_IMAGE:$LATEST_VERSION doesn't exist."
44 |     exit 1
45 |   fi
46 | }
47 | 
48 | # main script
49 | 
50 | cd "$PROJECT_DIRECTORY"
51 | 
52 | if [ ! -z "$1" ]; then
53 |   build "$1" "$2"
54 | else
55 |   cd "$PACKAGE_DIRECTORY/builds"
56 | 
57 |   for DOCKER_FILE in */Dockerfile; do
58 |     build "${DOCKER_FILE%%/*}" "$2"
59 |   done
60 | fi
61 | 


--------------------------------------------------------------------------------
/scripts/ec2-build.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Perform a build using a AWS EC2 Spot Instance
 6 | #
 7 | # Usage: ./ec2-build.sh chromium|firefox stable|beta|dev version|tag
 8 | #
 9 | #
10 | # Further documentation: 
11 | # https://github.com/adieuadieu/serverless-chrome/blob/develop/docs/automation.md
12 | # https://github.com/adieuadieu/serverless-chrome/blob/develop/docs/chrome.md
13 | #
14 | 
15 | set -e
16 | 
17 | cd "$(dirname "$0")/.."
18 | 
19 | AWS_REGION=${AWS_REGION:-us-east-1}
20 | 
21 | PROJECT_DIRECTORY=$(pwd)
22 | BUILD_NAME=${1:-chromium}
23 | CHANNEL=${2:-stable}
24 | VERSION=${3:-master}
25 | DOCKER_ORG=${DOCKER_ORG:-adieuadieu}
26 | S3_BUCKET=${S3_BUCKET:-}
27 | FORCE_NEW_BUILD=${FORCE_NEW_BUILD:-}
28 | 
29 | #
30 | # Check for some required env variables
31 | # 
32 | if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
33 |   echo "Missing required AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY environment variables"
34 |   exit 1
35 | fi
36 | 
37 | if [ -z "$AWS_IAM_INSTANCE_ARN" ]; then
38 |   echo "Missing required AWS_IAM_INSTANCE_ARN environment variables"
39 |   exit 1
40 | fi
41 | 
42 | echo "Launching EC2 spot instance to build $BUILD_NAME version $VERSION ($CHANNEL channel)"
43 | 
44 | #
45 | # Pre-process and base64 encode user-data payload (a shell script)
46 | # run on instance startup via cloudinit
47 | #
48 | USER_DATA=$(sed -e "s/INSERT_CHANNEL_HERE/$CHANNEL/g" "$PROJECT_DIRECTORY/aws/user-data.sh" | \
49 |   sed -e "s/INSERT_BROWSER_HERE/$BUILD_NAME/g" | \
50 |   sed -e "s/INSERT_VERSION_HERE/$VERSION/g" | \
51 |   sed -e "s/INSERT_DOCKER_ORG_HERE/$DOCKER_ORG/g" | \
52 |   sed -e "s/INSERT_S3_BUCKET_HERE/$S3_BUCKET/g" | \
53 |   sed -e "s/INSERT_FORCE_NEW_BUILD_HERE/$FORCE_NEW_BUILD/g" | \
54 |   base64 \
55 | )
56 | 
57 | #
58 | # Setup JSON payload which sets/configures the AWS CLI command
59 | #
60 | JSON=$(jq -c -r \
61 |   ".LaunchSpecification.UserData |= \"$USER_DATA\" | .LaunchSpecification.IamInstanceProfile.Arn |= \"$AWS_IAM_INSTANCE_ARN\"" \
62 |   "$PROJECT_DIRECTORY/aws/ec2-spot-instance-specification.json"
63 | )
64 | 
65 | #
66 | # Request the spot instance / launch
67 | # ref: http://docs.aws.amazon.com/cli/latest/reference/ec2/request-spot-instances.html
68 | # --valid-until "2018-08-22T00:00:00.000Z" \
69 | # 
70 | aws ec2 request-spot-instances \
71 |   --region "$AWS_REGION" \
72 |   --valid-until "$(date -u +%FT%T.000Z -d '6 hours')" \
73 |   --cli-input-json "$JSON" | \
74 |   jq -r ".SpotInstanceRequests[].Status"
75 | 


--------------------------------------------------------------------------------
/scripts/link-package.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Link packages
 6 | #
 7 | # Usage: ./link-packages.sh packageDirectory linkedDependencyDirectory
 8 | #
 9 | 
10 | set -e
11 | 
12 | cd "$(dirname "$0")/../"
13 | 
14 | PROJECT_DIRECTORY=$(pwd)
15 | PACKAGE_PATH=$1
16 | LINKED_PACKAGE=$2
17 | 
18 | # cd packages/
19 | 
20 | # for PACKAGE in */package.json; do
21 | #   PACKAGE_NAME="${PACKAGE%%/*}"
22 | #   cd "$PROJECT_DIRECTORY/packages/$PACKAGE_NAME" || exit 1
23 | #   npm link
24 | # done
25 | 
26 | 
27 | cd "$PROJECT_DIRECTORY/$PACKAGE_PATH"
28 | npm link "$PROJECT_DIRECTORY/packages/$LINKED_PACKAGE"
29 | 


--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
  1 | #!/bin/sh
  2 | # shellcheck shell=dash
  3 | 
  4 | #
  5 | # Creates a GitHub release or pre-release for tagged commits.
  6 | #
  7 | # Requires git, curl and jq.
  8 | #
  9 | # Usage: ./release.sh
 10 | #
 11 | 
 12 | set -e
 13 | 
 14 | cd "$(dirname "$0")/../"
 15 | 
 16 | PROJECT_DIRECTORY=$(pwd)
 17 | PACKAGE_DIRECTORY="$PROJECT_DIRECTORY/packages/lambda"
 18 | 
 19 | if [ -z "$GITHUB_TOKEN" ]; then
 20 |   printf "Error: Missing %s environment variable.\n" \
 21 |     GITHUB_TOKEN >&2
 22 |   exit 1
 23 | fi
 24 | 
 25 | if ! npm whoami -s && [ -z "$NPM_TOKEN" ]; then
 26 |   echo "Error: Missing NPM credentials or NPM_TOKEN environment variable." 
 27 |   exit 1
 28 | fi
 29 | 
 30 | GITHUB_ORG=adieuadieu
 31 | GITHUB_REPO=serverless-chrome
 32 | export GITHUB_ORG
 33 | export GITHUB_REPO
 34 | 
 35 | # Get the tag for the current commit:
 36 | git fetch origin 'refs/tags/*:refs/tags/*'
 37 | TAG="$(git describe --exact-match --tags 2> /dev/null || true)"
 38 | 
 39 | if [ -z "$TAG" ]; then
 40 |   echo "Not a tagged commit. Skipping release.."
 41 |   exit
 42 | fi
 43 | 
 44 | # Check if this is a pre-release version (denoted by a hyphen):
 45 | if [ "${TAG#*-}" != "$TAG" ]; then
 46 |   PRE=true
 47 | else
 48 |   PRE=false
 49 | fi
 50 | 
 51 | RELEASE_TEMPLATE='{
 52 |   "tag_name": "%s",
 53 |   "name": "%s",
 54 |   "prerelease": %s,
 55 |   "draft": %s
 56 | }'
 57 | 
 58 | RELEASE_BODY="This is an automated release.\n\n"
 59 | 
 60 | create_release_draft() {
 61 |   # shellcheck disable=SC2034
 62 |   local ouput
 63 |   # shellcheck disable=SC2059
 64 |   if output=$(curl \
 65 |       --silent \
 66 |       --fail \
 67 |       --request POST \
 68 |       --header "Authorization: token $GITHUB_TOKEN" \
 69 |       --header 'Content-Type: application/json' \
 70 |       --data "$(printf "$RELEASE_TEMPLATE" "$TAG" "$TAG" "$PRE" true)" \
 71 |       "https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/releases");
 72 |   then
 73 |     RELEASE_ID=$(echo "$output" | jq -re '.id')
 74 |     UPLOAD_URL_TEMPLATE=$(echo "$output" | jq -re '.upload_url')
 75 |   fi
 76 | }
 77 | 
 78 | upload_release_asset() {
 79 |   # shellcheck disable=SC2059
 80 |   curl \
 81 |     --silent \
 82 |     --fail \
 83 |     --request POST \
 84 |     --header "Authorization: token $GITHUB_TOKEN" \
 85 |     --header 'Content-Type: application/zip' \
 86 |     --data-binary "@$1" \
 87 |     "${UPLOAD_URL_TEMPLATE%\{*}?name=$2&label=$1" \
 88 |     > /dev/null
 89 | }
 90 | 
 91 | update_release_body() {
 92 |   # shellcheck disable=SC2059
 93 |   curl \
 94 |     --silent \
 95 |     --fail \
 96 |     --request PATCH \
 97 |     --header "Authorization: token $GITHUB_TOKEN" \
 98 |     --header 'Content-Type: application/json' \
 99 |     --data "{\"body\":\"$RELEASE_BODY\"}" \
100 |     "https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/releases/$1" \
101 |     > /dev/null
102 | }
103 | 
104 | publish_release() {
105 |   # shellcheck disable=SC2059
106 |   curl \
107 |     --silent \
108 |     --fail \
109 |     --request PATCH \
110 |     --header "Authorization: token $GITHUB_TOKEN" \
111 |     --header 'Content-Type: application/json' \
112 |     --data "{\"draft\":false, \"tag_name\": \"$TAG\"}" \
113 |     "https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/releases/$1" \
114 |     > /dev/null
115 | }
116 | 
117 | echo "Creating release draft $TAG"
118 | create_release_draft
119 | 
120 | # upload zipped builds
121 | cd "$PACKAGE_DIRECTORY/builds"
122 | 
123 | for BUILD in */Dockerfile; do
124 |   BUILD_NAME="${BUILD%%/*}"
125 | 
126 |   cd "$PACKAGE_DIRECTORY/builds/$BUILD_NAME" || exit 1
127 | 
128 |   CHANNEL_LIST=$(jq -r ". | keys | tostring" ./version.json | sed -e 's/[^A-Za-z,]//g' | tr , '\n')
129 |   
130 |   while IFS= read -r CHANNEL; do
131 |     # Skip empty lines and lines starting with a hash (#):
132 |     [ -z "$CHANNEL" ] || [ "${CHANNEL#\#}" != "$CHANNEL" ] && continue
133 | 
134 |     VERSION=$(jq -r ".$CHANNEL" version.json)
135 |     ZIPFILE=$CHANNEL-headless-$BUILD_NAME-$VERSION-amazonlinux-2017-03.zip
136 | 
137 |     (
138 |       if [ ! -f "dist/$ZIPFILE" ]; then
139 |         echo "$BUILD_NAME version $VERSION has not been packaged. Packaging ..."
140 |         ../../scripts/package-binaries.sh "$BUILD_NAME" "$CHANNEL"
141 |       fi
142 | 
143 |       cd dist/
144 | 
145 |       echo "Uploading $ZIPFILE to GitHub"
146 | 
147 |       upload_release_asset "$ZIPFILE" "$CHANNEL-headless-$BUILD_NAME-amazonlinux-2.zip"
148 |     )
149 |   
150 |     RELEASE_BODY="$RELEASE_BODY$BUILD_NAME $VERSION ($CHANNEL channel) for Amazon Linux 2\n"
151 |   done << EOL
152 | $CHANNEL_LIST
153 | EOL
154 | 
155 | done
156 | 
157 | update_release_body "$RELEASE_ID"
158 | 
159 | publish_release "$RELEASE_ID"
160 | 
161 | 
162 | #
163 | # Publish NPM packages
164 | #
165 | 
166 | # Add NPM token to .npmrc if not logged in
167 | if [ -n "$NPM_TOKEN" ] && ! npm whoami -s; then
168 |   echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > "$HOME/.npmrc"
169 | fi
170 | 
171 | while IFS= read -r PACKAGE; do
172 |   cd "$PROJECT_DIRECTORY/packages/$PACKAGE"
173 |   npm publish
174 | done << EOL
175 | lambda
176 | serverless-plugin
177 | EOL
178 | 


--------------------------------------------------------------------------------
/scripts/sync-package-versions.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Synchronises package version with the version in the project root.
 6 | # Also synchronizes package dependencies
 7 | #
 8 | # Note: probably would be better to use something like Lerna here.........
 9 | #
10 | # Requires jq.
11 | #
12 | # Usage: ./sync-package-versions.sh [version]
13 | #
14 | 
15 | set -e
16 | 
17 | cd "$(dirname "$0")/../"
18 | 
19 | PROJECT_DIRECTORY=$(pwd)
20 | PROJECT_VERSION=$(jq -r ".version" package.json)
21 | VERSION=${1:-"$PROJECT_VERSION"}
22 | 
23 | update() {
24 |   PACKAGE_NAME="$1"
25 | 
26 |   cd "$PROJECT_DIRECTORY/$PACKAGE_NAME" || exit 1
27 |   
28 |   PACKAGE_VERSION=$(jq -r ".version" package.json)
29 | 
30 |   if [ "$PACKAGE_VERSION" != "$VERSION" ]; then
31 |     echo "Updating $PACKAGE_NAME version ..."
32 |     
33 |     JSON=$(jq -r \
34 |       ".version |= \"$VERSION\"" \
35 |       package.json
36 |     )
37 | 
38 |     HAS_LAMBDA_DEPENDENCY=$(echo "$JSON" | \
39 |       jq -r \
40 |       ".dependencies | has(\"@serverless-chrome/lambda\")"
41 |     )
42 | 
43 |     if [ "$HAS_LAMBDA_DEPENDENCY" = "true" ]; then
44 |       JSON=$(echo "$JSON" | \
45 |         jq -r \
46 |         ".dependencies.\"@serverless-chrome/lambda\" |= \"$VERSION\""
47 |       )
48 |     fi
49 | 
50 |     HAS_SERVERLESS_PLUGIN_DEPENDENCY=$(echo "$JSON" | \
51 |       jq -r \
52 |       ".devDependencies | has(\"serverless-plugin-chrome\")"
53 |     )
54 | 
55 |     if [ "$HAS_SERVERLESS_PLUGIN_DEPENDENCY" = "true" ]; then
56 |       JSON=$(echo "$JSON" | \
57 |         jq -r \
58 |         ".devDependencies.\"serverless-plugin-chrome\" |= \"$VERSION\""
59 |       )
60 |     fi
61 | 
62 |     echo "$JSON" > package.json
63 | 
64 |     # @TODO: run yarn to update lockfile
65 |     # chicken-before-the-egg problem. The following won't work because yarn
66 |     # will try to look for the new package version on the npm registry, but
67 |     # of course it won't find it because it's not been published yet..
68 |     #yarn --ignore-scripts --non-interactive
69 |   else
70 |     echo "$PACKAGE_NAME version $VERSION is already latest. Skipping.."
71 |   fi
72 | }
73 | 
74 | 
75 | #
76 | # Synchronize all packages
77 | #
78 | cd packages/
79 | 
80 | for PACKAGE in */package.json; do
81 |   PACKAGE_NAME="${PACKAGE%%/*}"
82 | 
83 |   update "packages/$PACKAGE_NAME"
84 | done
85 | 
86 | 
87 | #
88 | # Synchronize examples
89 | #
90 | update "examples/serverless-framework/aws"
91 | 


--------------------------------------------------------------------------------
/scripts/update-browser-versions.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | # shellcheck shell=dash
 3 | 
 4 | #
 5 | # Checks latest browser versions and updates configurations and commits to repo
 6 | #
 7 | # Requires git, curl and jq.
 8 | #
 9 | # Usage: ./update-browser-versions.sh
10 | #
11 | 
12 | set -e
13 | 
14 | cd "$(dirname "$0")/../"
15 | 
16 | PROJECT_DIRECTORY=$(pwd)
17 | PACKAGE_DIRECTORY="$PROJECT_DIRECTORY/packages/lambda"
18 | 
19 | UPDATES=0
20 | 
21 | 
22 | # on CircleCI, setup git user email & name
23 | if [ -z "$(git config user.email)" ] && [ -n "$GIT_USER_EMAIL" ]; then
24 |     git config --global user.email "$GIT_USER_EMAIL"
25 | fi
26 | 
27 | if [ -z "$(git config user.name)" ] && [ -n "$GIT_USER_NAME" ]; then
28 |     git config --global user.name "$GIT_USER_NAME"
29 | fi
30 | 
31 | git checkout master
32 | 
33 | 
34 | cd "$PACKAGE_DIRECTORY/builds"
35 | 
36 | for BUILD in */Dockerfile; do
37 |   BUILD_NAME="${BUILD%%/*}"
38 |   
39 |   cd "$BUILD_NAME" || exit
40 | 
41 |   CHANNEL_LIST=$(jq -r ". | keys | tostring" ./version.json | sed -e 's/[^A-Za-z,]//g' | tr , '\n')
42 |   DOCKER_IMAGE=headless-$BUILD_NAME-for-aws-lambda
43 | 
44 |   # Iterate over the channels:
45 |   while IFS= read -r CHANNEL; do
46 |     # Skip empty lines and lines starting with a hash (#):
47 |     [ -z "$CHANNEL" ] || [ "${CHANNEL#\#}" != "$CHANNEL" ] && continue
48 | 
49 |     CURRENT_VERSION=$(jq -r ".$CHANNEL" version.json)
50 |     LATEST_VERSION=$(./latest.sh "$CHANNEL")
51 | 
52 |     if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ]; then
53 |       if "$PROJECT_DIRECTORY/scripts/docker-image-exists.sh" "adieuadieu/$DOCKER_IMAGE" "$LATEST_VERSION"; then
54 |         echo "$BUILD_NAME has new version $LATEST_VERSION. Updating configuration ..."
55 |         
56 |         JSON=$(jq -r ".$CHANNEL |= \"$LATEST_VERSION\"" version.json)
57 |         echo "$JSON" > version.json
58 | 
59 |         git add version.json
60 |         git commit -m "chore ($BUILD_NAME): bump $CHANNEL channel version to $LATEST_VERSION" --no-verify
61 | 
62 |         # Only create new tag/release when stable channel has new version
63 |         if [ "$CHANNEL" = "stable" ]; then
64 |           UPDATES=1
65 |         fi
66 |       else
67 |         echo "Docker image for adieuadieu/$DOCKER_IMAGE:$LATEST_VERSION does not exist. Exiting." && exit 1
68 |       fi
69 |     else
70 |       echo "$BUILD_NAME $CHANNEL version $CURRENT_VERSION is already latest. Nothing to update."
71 |     fi
72 | 
73 |   done << EOL
74 | $CHANNEL_LIST
75 | EOL
76 | 
77 |   cd ../
78 | done
79 | 
80 | cd "$PROJECT_DIRECTORY"
81 | 
82 | # If there are new browser versions (on the stable channel) we create a new version
83 | if [ "$UPDATES" -eq 1 ]; then
84 |   npm version prerelease --no-git-tag-version # @TODO: change to 'minor' if stable-channel
85 |   
86 |   PROJECT_VERSION=$(jq -r ".version" package.json)
87 |   
88 |   ./scripts/sync-package-versions.sh
89 | 
90 |   git commit -a -m "v$PROJECT_VERSION"
91 |   git tag "v$PROJECT_VERSION"
92 |   git push --set-upstream origin master
93 |   git push --tags
94 | else
95 |   git push --set-upstream origin master
96 | fi
97 | 


--------------------------------------------------------------------------------