├── .gitignore ├── .prettierrc ├── CHANGES.md ├── Dockerfile ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── src └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 4.0.0 / 26-02-2021 2 | 3 | - _BREAKING_: Minimum Node 10.18.1 4 | - Update to Puppeteer 8.0.0 5 | - Update Koa and Winston to latest minor release 6 | - Update Docker image to Node 14 and alpine 3.12 7 | - Multi arch Docker image with arm64 support 8 | 9 | # 3.0.0 / 08-01-2020 10 | 11 | - No new changes from 3.0.0-beta.2 12 | 13 | # 3.0.0-beta.2 / 05-11-2019 14 | 15 | - _BREAKING_: Don't render screenshot for URLs that respond with error status. 16 | 17 | # 3.0.0-beta.1 / 04-11-2019 18 | 19 | - Support waiting for `document.fonts.ready` event. 20 | - Make `SCREENIE_SCREENSHOT_DELAY` environment optional. 21 | 22 | # 3.0.0-beta / 31-10-2019 23 | 24 | - Use Alpine as base Docker image 25 | - Update puppeteer to 1.19.0 26 | - Update Koa to 2.11.0 27 | - Update Winston to 3.2.1 28 | 29 | # 2.0.0 / 30-01-2018 30 | 31 | This is a major release which might require some more manual setup of 32 | Chromium to make use of. Don't upgrade to this before you've _checked the 33 | requirements_ of Chromium/Puppeteer, particularly when it comes to sandbox 34 | support in your kernel for security. 35 | 36 | - Switches to Chromium through Puppeteer over PhantomJS 37 | - Support a custom delay after page load before screenshot is generated 38 | - Support flag for enabling file protocol URLs 39 | 40 | # 1.2.0 / 16-10-2017 41 | 42 | - Add basic logging functionality 43 | - Handle SIGTERM gracefully, draining the pool 44 | - Added Dockerfile with CA certificate updates 45 | 46 | # 1.1.0 / 17-03-2017 47 | 48 | - Add PDF output support 49 | - Add support to customize the output format with a `format` request parameter 50 | 51 | # 1.0.0 / 06-02-2017 52 | 53 | - Initial public release 54 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine3.12 2 | 3 | ARG TARGETPLATFORM 4 | 5 | ENV SCREENIE_VERSION=4.0.0 6 | ENV SCREENIE_CHROMIUM_ARGS=--no-sandbox 7 | ENV SCREENIE_CHROMIUM_EXEC=/usr/lib/chromium/chrome 8 | ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true 9 | 10 | RUN mkdir -p /usr/src/app 11 | WORKDIR /usr/src/app 12 | 13 | # Installs latest Chromium package 14 | RUN apk update && apk upgrade && \ 15 | apk add --no-cache \ 16 | chromium \ 17 | nss \ 18 | freetype \ 19 | harfbuzz \ 20 | ttf-freefont \ 21 | font-noto-cjk \ 22 | git 23 | 24 | RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ 25 | wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64; else \ 26 | wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_aarch64; fi \ 27 | && chmod +x /usr/local/bin/dumb-init 28 | 29 | ENTRYPOINT ["dumb-init"] 30 | 31 | RUN npm install -g screenie-server@${SCREENIE_VERSION} --unsafe-perm 32 | 33 | EXPOSE 3000 34 | 35 | CMD /usr/local/bin/screenie 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2017 Screenie Server contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # screenie-server 2 | 3 | HTTP screenshot service based on [Puppeteer](https://github.com/GoogleChrome/puppeteer). 4 | 5 | Creates a HTTP server using [Koa](https://github.com/koajs/koa), by default on 6 | port 3000. It renders pages and creates screenshots of them on request. 7 | 8 | ## Installation / Usage 9 | 10 | You can install from npm and run the server manually: 11 | 12 | ```bash 13 | npm install screenie-server 14 | ./node_modules/.bin/screenie-server 15 | ``` 16 | 17 | Alternatively, we provide a Docker image (built from the 18 | [Dockerfile](Dockerfile)) at [eliksir/screenie-server](https://hub.docker.com/r/eliksir/screenie-server/). 19 | This container is not running in sandbox mode because the Docker image doesn't 20 | support user namespaces. 21 | 22 | ## Configuration 23 | 24 | Then request a screenshot of an URL using the `url` query parameter: 25 | `http://localhost:3000/?url=http://google.com/&format=jpeg` 26 | 27 | The size of the screenshot can be customized through the `width` and `height` 28 | query parameters, but will always be constrained within 2048x2048. The default 29 | size used when the parameters are missing can be customized by environment 30 | variables: 31 | 32 | * `SCREENIE_WIDTH`: Default width, as integer, in pixels (default `1024`). 33 | * `SCREENIE_HEIGHT`: Default height, as integer, in pixels (default `768`). 34 | 35 | The `format` query parameter can be used to request a specific format of the 36 | screenshot. The supported formats are PNG, JPEG and even PDF. You can 37 | also set the default format through an environment variable: 38 | 39 | * `SCREENIE_DEFAULT_FORMAT`: Default format (default `jpeg`). 40 | 41 | The Puppeteer pool can also be customized with environment variables: 42 | 43 | * `SCREENIE_POOL_MIN`: Minimum number of Puppeteer instances (default `2`). 44 | * `SCREENIE_POOL_MAX`: Maximum number of Puppeteer instances (default `10`). 45 | 46 | To control the level of logging that will be performed, customize the 47 | `SCREENIE_LOG_LEVEL` environment variable. Supported values are `error`, 48 | `warn`, `info`, `verbose`, `debug`, and `silly`, though only `info` and 49 | `verbose` are currently in use. 50 | 51 | * `SCREENIE_LOG_LEVEL`: Logging level (default `info`). 52 | 53 | To open up file scheme in URL parameter: 54 | 55 | * `SCREENIE_ALLOW_FILE_SCHEME`: true (default `false`). 56 | 57 | Delay from the `load` event until the screenshot is taken. This can solve 58 | issues with rendering (i.e. rendering webfonts) not being complete before the 59 | screenshot. 60 | 61 | * `SCREENIE_SCREENSHOT_DELAY`: Time in milliseconds (default `50`). 62 | 63 | And lastly, of course the HTTP port can be customized: 64 | 65 | * `SCREENIE_PORT`: HTTP port (default `3000`). 66 | 67 | ## Contributing 68 | 69 | We are open to contributions or suggestions. File issues or suggestions on the 70 | [GitHub issues page](https://github.com/eliksir/screenie-server/issues), and 71 | please do submit a pull request if you have the time to implement an 72 | improvement or bugfix. 73 | 74 | ## License 75 | 76 | Published under the MIT license. 77 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "screenie-server", 3 | "version": "4.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@dabh/diagnostics": { 8 | "version": "2.0.2", 9 | "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", 10 | "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", 11 | "requires": { 12 | "colorspace": "1.1.x", 13 | "enabled": "2.0.x", 14 | "kuler": "^2.0.0" 15 | } 16 | }, 17 | "@types/node": { 18 | "version": "14.14.31", 19 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", 20 | "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==", 21 | "optional": true 22 | }, 23 | "@types/yauzl": { 24 | "version": "2.9.1", 25 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", 26 | "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", 27 | "optional": true, 28 | "requires": { 29 | "@types/node": "*" 30 | } 31 | }, 32 | "accepts": { 33 | "version": "1.3.7", 34 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 35 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 36 | "requires": { 37 | "mime-types": "~2.1.24", 38 | "negotiator": "0.6.2" 39 | } 40 | }, 41 | "agent-base": { 42 | "version": "6.0.2", 43 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 44 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 45 | "requires": { 46 | "debug": "4" 47 | }, 48 | "dependencies": { 49 | "debug": { 50 | "version": "4.3.1", 51 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 52 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 53 | "requires": { 54 | "ms": "2.1.2" 55 | } 56 | }, 57 | "ms": { 58 | "version": "2.1.2", 59 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 60 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 61 | } 62 | } 63 | }, 64 | "any-promise": { 65 | "version": "1.3.0", 66 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 67 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" 68 | }, 69 | "async": { 70 | "version": "3.2.0", 71 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", 72 | "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" 73 | }, 74 | "balanced-match": { 75 | "version": "1.0.0", 76 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 77 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 78 | }, 79 | "base64-js": { 80 | "version": "1.5.1", 81 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 82 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 83 | }, 84 | "bl": { 85 | "version": "4.1.0", 86 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 87 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 88 | "requires": { 89 | "buffer": "^5.5.0", 90 | "inherits": "^2.0.4", 91 | "readable-stream": "^3.4.0" 92 | } 93 | }, 94 | "brace-expansion": { 95 | "version": "1.1.11", 96 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 97 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 98 | "requires": { 99 | "balanced-match": "^1.0.0", 100 | "concat-map": "0.0.1" 101 | } 102 | }, 103 | "buffer": { 104 | "version": "5.7.1", 105 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 106 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 107 | "requires": { 108 | "base64-js": "^1.3.1", 109 | "ieee754": "^1.1.13" 110 | } 111 | }, 112 | "buffer-crc32": { 113 | "version": "0.2.13", 114 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 115 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" 116 | }, 117 | "cache-content-type": { 118 | "version": "1.0.1", 119 | "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", 120 | "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", 121 | "requires": { 122 | "mime-types": "^2.1.18", 123 | "ylru": "^1.2.0" 124 | } 125 | }, 126 | "chownr": { 127 | "version": "1.1.4", 128 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 129 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 130 | }, 131 | "co": { 132 | "version": "4.6.0", 133 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 134 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 135 | }, 136 | "color": { 137 | "version": "3.0.0", 138 | "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", 139 | "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", 140 | "requires": { 141 | "color-convert": "^1.9.1", 142 | "color-string": "^1.5.2" 143 | } 144 | }, 145 | "color-convert": { 146 | "version": "1.9.3", 147 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 148 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 149 | "requires": { 150 | "color-name": "1.1.3" 151 | } 152 | }, 153 | "color-name": { 154 | "version": "1.1.3", 155 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 156 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 157 | }, 158 | "color-string": { 159 | "version": "1.5.4", 160 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", 161 | "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", 162 | "requires": { 163 | "color-name": "^1.0.0", 164 | "simple-swizzle": "^0.2.2" 165 | } 166 | }, 167 | "colors": { 168 | "version": "1.4.0", 169 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 170 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 171 | }, 172 | "colorspace": { 173 | "version": "1.1.2", 174 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", 175 | "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", 176 | "requires": { 177 | "color": "3.0.x", 178 | "text-hex": "1.0.x" 179 | } 180 | }, 181 | "concat-map": { 182 | "version": "0.0.1", 183 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 184 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 185 | }, 186 | "content-disposition": { 187 | "version": "0.5.3", 188 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 189 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 190 | "requires": { 191 | "safe-buffer": "5.1.2" 192 | } 193 | }, 194 | "content-type": { 195 | "version": "1.0.4", 196 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 197 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 198 | }, 199 | "cookies": { 200 | "version": "0.8.0", 201 | "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", 202 | "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", 203 | "requires": { 204 | "depd": "~2.0.0", 205 | "keygrip": "~1.1.0" 206 | } 207 | }, 208 | "core-util-is": { 209 | "version": "1.0.2", 210 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 211 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 212 | }, 213 | "debug": { 214 | "version": "3.1.0", 215 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 216 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 217 | "requires": { 218 | "ms": "2.0.0" 219 | } 220 | }, 221 | "deep-equal": { 222 | "version": "1.0.1", 223 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 224 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" 225 | }, 226 | "delegates": { 227 | "version": "1.0.0", 228 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 229 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 230 | }, 231 | "depd": { 232 | "version": "2.0.0", 233 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 234 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 235 | }, 236 | "destroy": { 237 | "version": "1.0.4", 238 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 239 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 240 | }, 241 | "devtools-protocol": { 242 | "version": "0.0.854822", 243 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz", 244 | "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==" 245 | }, 246 | "ee-first": { 247 | "version": "1.1.1", 248 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 249 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 250 | }, 251 | "enabled": { 252 | "version": "2.0.0", 253 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", 254 | "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" 255 | }, 256 | "encodeurl": { 257 | "version": "1.0.2", 258 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 259 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 260 | }, 261 | "end-of-stream": { 262 | "version": "1.4.4", 263 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 264 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 265 | "requires": { 266 | "once": "^1.4.0" 267 | } 268 | }, 269 | "escape-html": { 270 | "version": "1.0.3", 271 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 272 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 273 | }, 274 | "extract-zip": { 275 | "version": "2.0.1", 276 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", 277 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", 278 | "requires": { 279 | "@types/yauzl": "^2.9.1", 280 | "debug": "^4.1.1", 281 | "get-stream": "^5.1.0", 282 | "yauzl": "^2.10.0" 283 | }, 284 | "dependencies": { 285 | "debug": { 286 | "version": "4.3.1", 287 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 288 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 289 | "requires": { 290 | "ms": "2.1.2" 291 | } 292 | }, 293 | "ms": { 294 | "version": "2.1.2", 295 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 296 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 297 | } 298 | } 299 | }, 300 | "fast-safe-stringify": { 301 | "version": "2.0.7", 302 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", 303 | "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" 304 | }, 305 | "fd-slicer": { 306 | "version": "1.1.0", 307 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 308 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 309 | "requires": { 310 | "pend": "~1.2.0" 311 | } 312 | }, 313 | "fecha": { 314 | "version": "4.2.0", 315 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", 316 | "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" 317 | }, 318 | "find-up": { 319 | "version": "4.1.0", 320 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 321 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 322 | "requires": { 323 | "locate-path": "^5.0.0", 324 | "path-exists": "^4.0.0" 325 | } 326 | }, 327 | "fn.name": { 328 | "version": "1.1.0", 329 | "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", 330 | "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" 331 | }, 332 | "fresh": { 333 | "version": "0.5.2", 334 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 335 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 336 | }, 337 | "fs-constants": { 338 | "version": "1.0.0", 339 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 340 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 341 | }, 342 | "fs.realpath": { 343 | "version": "1.0.0", 344 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 345 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 346 | }, 347 | "generic-pool": { 348 | "version": "3.7.8", 349 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.7.8.tgz", 350 | "integrity": "sha512-Pz93INFSbhjEROVbM91rurD05G+Kx8833rG+lVU57mznEBpzkc1f5/g+d511a1Yf8dbAEsm7DDA3QLytMFbiGA==" 351 | }, 352 | "get-stream": { 353 | "version": "5.2.0", 354 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", 355 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 356 | "requires": { 357 | "pump": "^3.0.0" 358 | } 359 | }, 360 | "glob": { 361 | "version": "7.1.6", 362 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 363 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 364 | "requires": { 365 | "fs.realpath": "^1.0.0", 366 | "inflight": "^1.0.4", 367 | "inherits": "2", 368 | "minimatch": "^3.0.4", 369 | "once": "^1.3.0", 370 | "path-is-absolute": "^1.0.0" 371 | } 372 | }, 373 | "http-assert": { 374 | "version": "1.4.1", 375 | "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", 376 | "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", 377 | "requires": { 378 | "deep-equal": "~1.0.1", 379 | "http-errors": "~1.7.2" 380 | }, 381 | "dependencies": { 382 | "depd": { 383 | "version": "1.1.2", 384 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 385 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 386 | }, 387 | "http-errors": { 388 | "version": "1.7.3", 389 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", 390 | "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", 391 | "requires": { 392 | "depd": "~1.1.2", 393 | "inherits": "2.0.4", 394 | "setprototypeof": "1.1.1", 395 | "statuses": ">= 1.5.0 < 2", 396 | "toidentifier": "1.0.0" 397 | } 398 | } 399 | } 400 | }, 401 | "http-errors": { 402 | "version": "1.8.0", 403 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", 404 | "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", 405 | "requires": { 406 | "depd": "~1.1.2", 407 | "inherits": "2.0.4", 408 | "setprototypeof": "1.2.0", 409 | "statuses": ">= 1.5.0 < 2", 410 | "toidentifier": "1.0.0" 411 | }, 412 | "dependencies": { 413 | "depd": { 414 | "version": "1.1.2", 415 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 416 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 417 | }, 418 | "setprototypeof": { 419 | "version": "1.2.0", 420 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 421 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 422 | } 423 | } 424 | }, 425 | "https-proxy-agent": { 426 | "version": "5.0.0", 427 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 428 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 429 | "requires": { 430 | "agent-base": "6", 431 | "debug": "4" 432 | }, 433 | "dependencies": { 434 | "debug": { 435 | "version": "4.3.1", 436 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 437 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 438 | "requires": { 439 | "ms": "2.1.2" 440 | } 441 | }, 442 | "ms": { 443 | "version": "2.1.2", 444 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 445 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 446 | } 447 | } 448 | }, 449 | "ieee754": { 450 | "version": "1.2.1", 451 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 452 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 453 | }, 454 | "inflight": { 455 | "version": "1.0.6", 456 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 457 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 458 | "requires": { 459 | "once": "^1.3.0", 460 | "wrappy": "1" 461 | } 462 | }, 463 | "inherits": { 464 | "version": "2.0.4", 465 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 466 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 467 | }, 468 | "is-arrayish": { 469 | "version": "0.3.2", 470 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 471 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 472 | }, 473 | "is-generator-function": { 474 | "version": "1.0.8", 475 | "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", 476 | "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==" 477 | }, 478 | "is-stream": { 479 | "version": "2.0.0", 480 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", 481 | "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" 482 | }, 483 | "isarray": { 484 | "version": "1.0.0", 485 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 486 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 487 | }, 488 | "keygrip": { 489 | "version": "1.1.0", 490 | "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", 491 | "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", 492 | "requires": { 493 | "tsscmp": "1.0.6" 494 | } 495 | }, 496 | "koa": { 497 | "version": "2.13.1", 498 | "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.1.tgz", 499 | "integrity": "sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==", 500 | "requires": { 501 | "accepts": "^1.3.5", 502 | "cache-content-type": "^1.0.0", 503 | "content-disposition": "~0.5.2", 504 | "content-type": "^1.0.4", 505 | "cookies": "~0.8.0", 506 | "debug": "~3.1.0", 507 | "delegates": "^1.0.0", 508 | "depd": "^2.0.0", 509 | "destroy": "^1.0.4", 510 | "encodeurl": "^1.0.2", 511 | "escape-html": "^1.0.3", 512 | "fresh": "~0.5.2", 513 | "http-assert": "^1.3.0", 514 | "http-errors": "^1.6.3", 515 | "is-generator-function": "^1.0.7", 516 | "koa-compose": "^4.1.0", 517 | "koa-convert": "^1.2.0", 518 | "on-finished": "^2.3.0", 519 | "only": "~0.0.2", 520 | "parseurl": "^1.3.2", 521 | "statuses": "^1.5.0", 522 | "type-is": "^1.6.16", 523 | "vary": "^1.1.2" 524 | } 525 | }, 526 | "koa-compose": { 527 | "version": "4.1.0", 528 | "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", 529 | "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" 530 | }, 531 | "koa-convert": { 532 | "version": "1.2.0", 533 | "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", 534 | "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", 535 | "requires": { 536 | "co": "^4.6.0", 537 | "koa-compose": "^3.0.0" 538 | }, 539 | "dependencies": { 540 | "koa-compose": { 541 | "version": "3.2.1", 542 | "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", 543 | "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", 544 | "requires": { 545 | "any-promise": "^1.1.0" 546 | } 547 | } 548 | } 549 | }, 550 | "kuler": { 551 | "version": "2.0.0", 552 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", 553 | "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" 554 | }, 555 | "locate-path": { 556 | "version": "5.0.0", 557 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 558 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 559 | "requires": { 560 | "p-locate": "^4.1.0" 561 | } 562 | }, 563 | "logform": { 564 | "version": "2.2.0", 565 | "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", 566 | "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", 567 | "requires": { 568 | "colors": "^1.2.1", 569 | "fast-safe-stringify": "^2.0.4", 570 | "fecha": "^4.2.0", 571 | "ms": "^2.1.1", 572 | "triple-beam": "^1.3.0" 573 | }, 574 | "dependencies": { 575 | "ms": { 576 | "version": "2.1.3", 577 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 578 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 579 | } 580 | } 581 | }, 582 | "media-typer": { 583 | "version": "0.3.0", 584 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 585 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 586 | }, 587 | "mime-db": { 588 | "version": "1.46.0", 589 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", 590 | "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==" 591 | }, 592 | "mime-types": { 593 | "version": "2.1.29", 594 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", 595 | "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", 596 | "requires": { 597 | "mime-db": "1.46.0" 598 | } 599 | }, 600 | "minimatch": { 601 | "version": "3.0.4", 602 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 603 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 604 | "requires": { 605 | "brace-expansion": "^1.1.7" 606 | } 607 | }, 608 | "mkdirp-classic": { 609 | "version": "0.5.3", 610 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 611 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 612 | }, 613 | "ms": { 614 | "version": "2.0.0", 615 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 616 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 617 | }, 618 | "negotiator": { 619 | "version": "0.6.2", 620 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 621 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 622 | }, 623 | "node-fetch": { 624 | "version": "2.6.1", 625 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 626 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 627 | }, 628 | "on-finished": { 629 | "version": "2.3.0", 630 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 631 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 632 | "requires": { 633 | "ee-first": "1.1.1" 634 | } 635 | }, 636 | "once": { 637 | "version": "1.4.0", 638 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 639 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 640 | "requires": { 641 | "wrappy": "1" 642 | } 643 | }, 644 | "one-time": { 645 | "version": "1.0.0", 646 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", 647 | "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", 648 | "requires": { 649 | "fn.name": "1.x.x" 650 | } 651 | }, 652 | "only": { 653 | "version": "0.0.2", 654 | "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", 655 | "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" 656 | }, 657 | "p-limit": { 658 | "version": "2.3.0", 659 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 660 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 661 | "requires": { 662 | "p-try": "^2.0.0" 663 | } 664 | }, 665 | "p-locate": { 666 | "version": "4.1.0", 667 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 668 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 669 | "requires": { 670 | "p-limit": "^2.2.0" 671 | } 672 | }, 673 | "p-try": { 674 | "version": "2.2.0", 675 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 676 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 677 | }, 678 | "parseurl": { 679 | "version": "1.3.3", 680 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 681 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 682 | }, 683 | "path-exists": { 684 | "version": "4.0.0", 685 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 686 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 687 | }, 688 | "path-is-absolute": { 689 | "version": "1.0.1", 690 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 691 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 692 | }, 693 | "pend": { 694 | "version": "1.2.0", 695 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 696 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" 697 | }, 698 | "pkg-dir": { 699 | "version": "4.2.0", 700 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 701 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 702 | "requires": { 703 | "find-up": "^4.0.0" 704 | } 705 | }, 706 | "process-nextick-args": { 707 | "version": "2.0.1", 708 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 709 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 710 | }, 711 | "progress": { 712 | "version": "2.0.3", 713 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 714 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 715 | }, 716 | "proxy-from-env": { 717 | "version": "1.1.0", 718 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 719 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 720 | }, 721 | "pump": { 722 | "version": "3.0.0", 723 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 724 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 725 | "requires": { 726 | "end-of-stream": "^1.1.0", 727 | "once": "^1.3.1" 728 | } 729 | }, 730 | "puppeteer": { 731 | "version": "8.0.0", 732 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz", 733 | "integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==", 734 | "requires": { 735 | "debug": "^4.1.0", 736 | "devtools-protocol": "0.0.854822", 737 | "extract-zip": "^2.0.0", 738 | "https-proxy-agent": "^5.0.0", 739 | "node-fetch": "^2.6.1", 740 | "pkg-dir": "^4.2.0", 741 | "progress": "^2.0.1", 742 | "proxy-from-env": "^1.1.0", 743 | "rimraf": "^3.0.2", 744 | "tar-fs": "^2.0.0", 745 | "unbzip2-stream": "^1.3.3", 746 | "ws": "^7.2.3" 747 | }, 748 | "dependencies": { 749 | "debug": { 750 | "version": "4.3.1", 751 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 752 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 753 | "requires": { 754 | "ms": "2.1.2" 755 | } 756 | }, 757 | "ms": { 758 | "version": "2.1.2", 759 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 760 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 761 | } 762 | } 763 | }, 764 | "puppeteer-pool": { 765 | "version": "github:eliksir/puppeteer-pool#9025c3b41dfb0b9d6e2940917c60142c61899a8d", 766 | "from": "github:eliksir/puppeteer-pool#v1.3.1", 767 | "requires": { 768 | "debug": "^2.3.3", 769 | "generic-pool": "^3.1.4" 770 | }, 771 | "dependencies": { 772 | "debug": { 773 | "version": "2.6.9", 774 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 775 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 776 | "requires": { 777 | "ms": "2.0.0" 778 | } 779 | } 780 | } 781 | }, 782 | "readable-stream": { 783 | "version": "3.6.0", 784 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 785 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 786 | "requires": { 787 | "inherits": "^2.0.3", 788 | "string_decoder": "^1.1.1", 789 | "util-deprecate": "^1.0.1" 790 | } 791 | }, 792 | "rimraf": { 793 | "version": "3.0.2", 794 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 795 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 796 | "requires": { 797 | "glob": "^7.1.3" 798 | } 799 | }, 800 | "safe-buffer": { 801 | "version": "5.1.2", 802 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 803 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 804 | }, 805 | "setprototypeof": { 806 | "version": "1.1.1", 807 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 808 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 809 | }, 810 | "simple-swizzle": { 811 | "version": "0.2.2", 812 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 813 | "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", 814 | "requires": { 815 | "is-arrayish": "^0.3.1" 816 | } 817 | }, 818 | "stack-trace": { 819 | "version": "0.0.10", 820 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 821 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 822 | }, 823 | "statuses": { 824 | "version": "1.5.0", 825 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 826 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 827 | }, 828 | "string_decoder": { 829 | "version": "1.3.0", 830 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 831 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 832 | "requires": { 833 | "safe-buffer": "~5.2.0" 834 | }, 835 | "dependencies": { 836 | "safe-buffer": { 837 | "version": "5.2.1", 838 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 839 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 840 | } 841 | } 842 | }, 843 | "tar-fs": { 844 | "version": "2.1.1", 845 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", 846 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", 847 | "requires": { 848 | "chownr": "^1.1.1", 849 | "mkdirp-classic": "^0.5.2", 850 | "pump": "^3.0.0", 851 | "tar-stream": "^2.1.4" 852 | } 853 | }, 854 | "tar-stream": { 855 | "version": "2.2.0", 856 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 857 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 858 | "requires": { 859 | "bl": "^4.0.3", 860 | "end-of-stream": "^1.4.1", 861 | "fs-constants": "^1.0.0", 862 | "inherits": "^2.0.3", 863 | "readable-stream": "^3.1.1" 864 | } 865 | }, 866 | "text-hex": { 867 | "version": "1.0.0", 868 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", 869 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" 870 | }, 871 | "through": { 872 | "version": "2.3.8", 873 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 874 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 875 | }, 876 | "toidentifier": { 877 | "version": "1.0.0", 878 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 879 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 880 | }, 881 | "triple-beam": { 882 | "version": "1.3.0", 883 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", 884 | "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" 885 | }, 886 | "tsscmp": { 887 | "version": "1.0.6", 888 | "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", 889 | "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" 890 | }, 891 | "type-is": { 892 | "version": "1.6.18", 893 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 894 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 895 | "requires": { 896 | "media-typer": "0.3.0", 897 | "mime-types": "~2.1.24" 898 | } 899 | }, 900 | "unbzip2-stream": { 901 | "version": "1.4.3", 902 | "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", 903 | "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", 904 | "requires": { 905 | "buffer": "^5.2.1", 906 | "through": "^2.3.8" 907 | } 908 | }, 909 | "util-deprecate": { 910 | "version": "1.0.2", 911 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 912 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 913 | }, 914 | "vary": { 915 | "version": "1.1.2", 916 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 917 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 918 | }, 919 | "winston": { 920 | "version": "3.3.3", 921 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", 922 | "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", 923 | "requires": { 924 | "@dabh/diagnostics": "^2.0.2", 925 | "async": "^3.1.0", 926 | "is-stream": "^2.0.0", 927 | "logform": "^2.2.0", 928 | "one-time": "^1.0.0", 929 | "readable-stream": "^3.4.0", 930 | "stack-trace": "0.0.x", 931 | "triple-beam": "^1.3.0", 932 | "winston-transport": "^4.4.0" 933 | } 934 | }, 935 | "winston-transport": { 936 | "version": "4.4.0", 937 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", 938 | "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", 939 | "requires": { 940 | "readable-stream": "^2.3.7", 941 | "triple-beam": "^1.2.0" 942 | }, 943 | "dependencies": { 944 | "readable-stream": { 945 | "version": "2.3.7", 946 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 947 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 948 | "requires": { 949 | "core-util-is": "~1.0.0", 950 | "inherits": "~2.0.3", 951 | "isarray": "~1.0.0", 952 | "process-nextick-args": "~2.0.0", 953 | "safe-buffer": "~5.1.1", 954 | "string_decoder": "~1.1.1", 955 | "util-deprecate": "~1.0.1" 956 | } 957 | }, 958 | "string_decoder": { 959 | "version": "1.1.1", 960 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 961 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 962 | "requires": { 963 | "safe-buffer": "~5.1.0" 964 | } 965 | } 966 | } 967 | }, 968 | "wrappy": { 969 | "version": "1.0.2", 970 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 971 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 972 | }, 973 | "ws": { 974 | "version": "7.4.6", 975 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", 976 | "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" 977 | }, 978 | "yauzl": { 979 | "version": "2.10.0", 980 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 981 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 982 | "requires": { 983 | "buffer-crc32": "~0.2.3", 984 | "fd-slicer": "~1.1.0" 985 | } 986 | }, 987 | "ylru": { 988 | "version": "1.2.1", 989 | "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", 990 | "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==" 991 | } 992 | } 993 | } 994 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "screenie-server", 3 | "version": "4.0.0", 4 | "description": "A screenshot HTTP service using Puppeteer", 5 | "author": "Frode Danielsen ", 6 | "keywords": [ 7 | "screenshot", 8 | "puppeteer", 9 | "koa" 10 | ], 11 | "main": "src/server.js", 12 | "bin": { 13 | "screenie": "src/server.js" 14 | }, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">=10.18.1" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/eliksir/screenie-server.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/eliksir/screenie-server/issues" 28 | }, 29 | "dependencies": { 30 | "koa": "2.13.1", 31 | "puppeteer": "8.0.0", 32 | "puppeteer-pool": "eliksir/puppeteer-pool#v1.3.1", 33 | "winston": "3.3.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Koa = require('koa'); 4 | const winston = require('winston'); 5 | const createPuppeteerPool = require('puppeteer-pool').default; 6 | const { combine, timestamp, printf } = winston.format; 7 | 8 | const loggerFormat = printf(({ message, timestamp }) => { 9 | return `[${timestamp}] ${message}`; 10 | }); 11 | 12 | const logger = winston.createLogger({ 13 | level: process.env.SCREENIE_LOG_LEVEL || 'info', 14 | format: combine(timestamp(), loggerFormat), 15 | transports: [ 16 | new winston.transports.Console({ 17 | timestamp: () => new Date().toISOString(), 18 | }), 19 | ], 20 | }); 21 | 22 | logger.log('verbose', 'Setting up defaults from environment'); 23 | const chromiumArgs = process.env.SCREENIE_CHROMIUM_ARGS 24 | ? { args: process.env.SCREENIE_CHROMIUM_ARGS.split(' ') } 25 | : {}; 26 | const chromiumExec = process.env.SCREENIE_CHROMIUM_EXEC 27 | ? { executablePath: process.env.SCREENIE_CHROMIUM_EXEC } 28 | : {}; 29 | const defaultFormat = process.env.SCREENIE_DEFAULT_FORMAT || 'jpeg'; 30 | const imageSize = { 31 | width: process.env.SCREENIE_WIDTH || 1024, 32 | height: process.env.SCREENIE_HEIGHT || 768, 33 | }; 34 | const serverPort = process.env.SCREENIE_PORT || 3000; 35 | const supportedFormats = ['jpg', 'jpeg', 'pdf', 'png']; 36 | const allowFileScheme = process.env.SCREENIE_ALLOW_FILE_SCHEME || false; 37 | 38 | const app = new Koa(); 39 | logger.log('verbose', 'Created KOA server'); 40 | 41 | const pool = createPuppeteerPool({ 42 | min: process.env.SCREENIE_POOL_MIN || 2, 43 | max: process.env.SCREENIE_POOL_MAX || 10, 44 | puppeteerArgs: Object.assign({}, chromiumArgs, chromiumExec), 45 | }); 46 | 47 | const screenshotDelay = process.env.SCREENIE_SCREENSHOT_DELAY; 48 | 49 | logger.log('verbose', 'Created Puppeteer pool'); 50 | 51 | /* 52 | * Clean up the Puppeteer pool before exiting when receiving a termination 53 | * signal. Exit with status code 143 (128 + SIGTERM's signal number, 15). 54 | */ 55 | process.on('SIGTERM', () => { 56 | logger.log('info', 'Received SIGTERM, exiting...'); 57 | pool 58 | .drain() 59 | .then(() => pool.clear()) 60 | .then(() => process.exit(143)); 61 | }); 62 | 63 | /** 64 | * Set up a Puppeteer instance for a page and configure viewport size. 65 | */ 66 | app.use(async (ctx, next) => { 67 | const { width, height } = ctx.request.query; 68 | const size = { 69 | width: Math.min(2048, parseInt(width, 10) || imageSize.width), 70 | height: Math.min(2048, parseInt(height, 10) || imageSize.height), 71 | }; 72 | let pageError; 73 | 74 | logger.log( 75 | 'verbose', 76 | `Instantiating Page with size ${size.width}x${size.height}` 77 | ); 78 | 79 | await pool.use(instance => { 80 | const pid = instance.process().pid; 81 | logger.log('verbose', `Using browser instance with PID ${pid}`); 82 | return instance 83 | .newPage() 84 | .then(page => { 85 | logger.log('verbose', 'Set page instance on state'); 86 | ctx.state.page = page; 87 | }) 88 | .then(() => { 89 | logger.log('verbose', 'Set viewport for page'); 90 | return ctx.state.page.setViewport(size); 91 | }) 92 | .catch(error => { 93 | pageError = error; 94 | logger.log('verbose', `Invalidating instance with PID ${pid}`); 95 | pool.invalidate(instance); 96 | }); 97 | }); 98 | 99 | if (pageError) { 100 | ctx.throw(400, `Could not open a page: ${pageError.message}`); 101 | } 102 | 103 | await next(); 104 | }); 105 | 106 | /** 107 | * Attempt to load given URL in the Puppeteer page. 108 | * 109 | * Throws 400 Bad Request if no URL is provided, and 404 Not Found if 110 | * Puppeteer could not load the URL. 111 | */ 112 | app.use(async (ctx, next) => { 113 | const { page } = ctx.state; 114 | const { url } = ctx.request.query; 115 | 116 | let errorStatus = null; 117 | 118 | if (!url) { 119 | ctx.throw(400, 'No url request parameter supplied.'); 120 | } 121 | 122 | if (url.indexOf('file://') >= 0 && !allowFileScheme) { 123 | ctx.throw(403); 124 | } 125 | 126 | logger.log('verbose', `Attempting to load ${url}`); 127 | 128 | try { 129 | const response = await page.goto(url); 130 | const status = response.status(); 131 | 132 | if (status < 200 || status > 299) { 133 | errorStatus = status; 134 | throw new Error('Non-OK server response'); 135 | } 136 | 137 | await page.evaluateHandle('document.fonts.ready'); 138 | 139 | if (screenshotDelay) { 140 | await new Promise(resolve => setTimeout(resolve, screenshotDelay)); 141 | } 142 | } catch (error) { 143 | // Sets a catch-all error status for cases where `page.goto` throws 144 | if (!errorStatus) { 145 | errorStatus = 500; 146 | } 147 | } 148 | 149 | if (errorStatus) { 150 | ctx.throw(errorStatus); 151 | } 152 | 153 | await next(); 154 | }); 155 | 156 | /** 157 | * Determine the format of the output based on the `format` query parameter. 158 | * 159 | * The format must be among the formats supported by Puppeteer, else 400 160 | * Bad Request is thrown. If no format is provided, the default is used. 161 | */ 162 | app.use(async (ctx, next) => { 163 | const { format = defaultFormat } = ctx.request.query; 164 | 165 | if (supportedFormats.indexOf(format.toLowerCase()) === -1) { 166 | ctx.throw(400, `Format ${format} not supported.`); 167 | } 168 | 169 | ctx.type = ctx.state.format = format; 170 | 171 | await next(); 172 | }); 173 | 174 | /** 175 | * Generate a screenshot of the loaded page. 176 | * 177 | * If successful the screenshot is sent as the response. 178 | */ 179 | app.use(async (ctx, next) => { 180 | const { url, fullPage } = ctx.request.query; 181 | const { format, page } = ctx.state; 182 | const { width, height } = page.viewport(); 183 | let renderError; 184 | 185 | logger.log('info', `Rendering screenshot of ${url} to ${format}`); 186 | 187 | if (format === 'pdf') { 188 | await page 189 | .pdf({ 190 | format: 'A4', 191 | margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }, 192 | }) 193 | .then(response => (ctx.body = response)) 194 | .catch(error => (renderError = error)); 195 | } else { 196 | let clipInfo = 197 | fullPage === '1' 198 | ? { fullPage: true } 199 | : { clip: { x: 0, y: 0, width: width, height: height } }; 200 | await page 201 | .screenshot( 202 | Object.assign( 203 | { 204 | type: format === 'jpg' ? 'jpeg' : format, 205 | omitBackground: true, 206 | }, 207 | clipInfo 208 | ) 209 | ) 210 | .then(response => (ctx.body = response)) 211 | .catch(error => (renderError = error)); 212 | } 213 | 214 | if (renderError) { 215 | ctx.throw(400, `Could not render page: ${renderError.message}`); 216 | } 217 | 218 | await page.close(); 219 | 220 | await next(); 221 | }); 222 | 223 | /** 224 | * Error handler to make sure page is getting closed. 225 | */ 226 | app.on('error', (error, context) => { 227 | const { page } = context.state; 228 | 229 | if (page) { 230 | page.close(); 231 | } 232 | 233 | logger.log('error', error.message); 234 | }); 235 | 236 | app.listen(serverPort); 237 | logger.log('info', `Screenie server started on port ${serverPort}`); 238 | --------------------------------------------------------------------------------