├── .firebaserc ├── .gitignore ├── .npmignore ├── README.md ├── assets └── rocket-orange.png ├── firebase.json ├── functions ├── index.js ├── package-lock.json └── package.json ├── package.json ├── src ├── api │ ├── index.ts │ ├── interfaces.ts │ ├── item.ts │ ├── stories.ts │ └── user.ts ├── cli.ts ├── index.ts ├── offline │ ├── api.ts │ └── build.ts ├── publish │ └── index.ts ├── server.ts └── test │ ├── offline.test.ts │ ├── publish.test.ts │ └── serve.test.ts ├── tsconfig.json └── yarn.lock /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "hnpwa-api", 4 | "prod": "hnpwa-api" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | functions/node_modules 2 | node_modules 3 | public 4 | dist 5 | database.rules.json 6 | .DS_Store 7 | *.tgz 8 | *.log 9 | .firebase 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | offline/news.json 3 | offline/ask.json 4 | offline/show.json 5 | offline/newest.json 6 | offline/jobs.json 7 | offline/news 8 | offline/ask 9 | offline/show 10 | offline/newest 11 | offline/jobs 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HNPWA API 2 | 3 |

4 | 5 | 6 | 7 | Deploy a CDN cached Hacker News API to your own Firebase Hosting Domain. All in two lines of code 😎 8 | 9 | **Heavily** inspired/guided by [cheeaun's](https://github.com/cheeaun) [node-hnapi](https://github.com/cheeaun/node-hnapi). 10 | 11 | ## Install 12 | ```bash 13 | npm i hnpwa-api 14 | ``` 15 | 16 | ## Basic usage 17 | Import and use in your `functions/index.js` file: 18 | ```js 19 | const hnapi = require('hnpwa-api'); 20 | exports.api = hnapi.trigger({ 21 | useCors: false, // defaults to false 22 | useCompression: true, // defaults to true 23 | browserCacheExpiry: 300, // in seconds (5 min is the default) 24 | cdnCacheExpiry: 600, // in seconds (10 min is the default) 25 | staleWhileRevalidate: 120, // Allow CDN to serve stale data 120 seconds after cdnCacheExpiry 26 | firebaseAppName: 'hnpwa-api', // defaults to hnpwa-api 27 | offline: false, // Serves offline data if data is downloaded (See Global Module guide) 28 | routerPath: 'api', // provide a serving path ex: mysite.com/api/news.json 29 | }); 30 | ``` 31 | 32 | ## Why? 33 | Two reasons: **latency** and **same domain**. 34 | 35 | ### Latency 36 | This API is designed for Firebase Hosting which is backed by a global CDN. Responses are cached in edges around the globe which results in low latency. 37 | 38 | [Latency test: CDN cached vs. in memory cache](https://latency.apex.sh/?url=https%3A%2F%2Fhnpwa-api.firebaseapp.com%2Fnews.json%3Fpage%3D1&compare=https%3A%2F%2Fnode-hnapi.herokuapp.com%2Fnews%3Fpage%3D1) 39 | 40 | ### Same domain 41 | With HTTP/2 you reuse one connection per domain. This package allows you to easily deploy your own HNAPI on your own domain for one nice TCP connection. 42 | 43 | ## Setup Tutorial 44 | 45 | #### 1. Install the Firebase CLI: 46 | ```bash 47 | npm i -g firebase-tools 48 | ``` 49 | 50 | #### 2. Initialize Cloud Functions for Firebase 51 | ```bash 52 | firebase init functions 53 | ``` 54 | 55 | #### 3. Install hnpwa-api 56 | Inside of the `functions` folder, install `hnpwa-api`: 57 | ```bash 58 | cd functions 59 | npm i hnpwa-api --save # not needed on npm 5 but you get what im sayin 60 | ``` 61 | 62 | #### 4. Add HNAPI endpoint 63 | Open `functions/index.js`, and configure your HNAPI. 64 | 65 | ```js 66 | const hnapi = require('hnpwa-api'); 67 | exports.api = hnapi.trigger({ 68 | useCors: false, // defaults to false 69 | useCompression: true, // defaults to true 70 | browserCacheExpiry: 300, // in seconds (5 min is the default) 71 | cdnCacheExpiry: 600, // in seconds (10 min is the default) 72 | staleWhileRevalidate: 120, // Allow CDN to serve stale data 120 seconds after cdnCacheExpiry 73 | firebaseAppName: 'hnpwa-api', // defaults to hnpwa-api 74 | offline: false, // Serves offline data if data is downloaded (See Global Module guide) 75 | routerPath: 'api', // provide a serving path ex: mysite.com/api/news.json 76 | }); 77 | ``` 78 | 79 | #### 5. Initialize Firebase Hosting 80 | ```bash 81 | firebase init hosting 82 | ``` 83 | 84 | #### 6. Create a redirect for the API function 85 | Open `firebase.json` and create a redirect to call out to the HNAPI: 86 | ```json 87 | { 88 | "hosting": { 89 | "public": "public", 90 | "rewrites": [{ 91 | "source": "**", 92 | "function": "api" 93 | }] 94 | } 95 | } 96 | ``` 97 | 98 | #### 7. Deploy! 99 | ```bash 100 | firebase deploy 101 | ``` 102 | 103 | That's all there is to it. Feel free to file an issue if you find a bug. 104 | 105 | ## Global Module use 106 | The hnpwa-api module can either be downloaded as a global module or used from the 107 | `node_modules/.bin/hnpwa-api` directory. 108 | 109 | ### Serving offline 110 | The global module provides the ability to save data locally for offline serving. If 111 | you're developing on a bus, airplane, or someother place without a connection you'll 112 | need this. 113 | 114 | ```bash 115 | # 1) Save to node_modules/hnpwa-api/offline (~10mb) 116 | node_modules/.bin/hnpwa-api --save 117 | # 2) Now serve offline 118 | node_modules/.bin/hnpwa-api --serve --offline 119 | ``` 120 | 121 | ## Non-Firebase setup 122 | Not using Cloud Functions or Firebase Hosting as your backend? No problem. This library still has you covered. 123 | 124 | ```js 125 | const hnapi = require('hnpwa-api'); 126 | // does not include any middleware like the trigger() call above 127 | const expressApp = hnapi.app(); // optionally provide a firebase app name 128 | expressApp.listen(3000, () => console.log('Listening all on my own!')); 129 | ``` 130 | 131 | This returns an express app instance with the expected HN API endpoints. No middleware is attached unlike the `trigger(config)` method. 132 | 133 | ### Why do I need a Firebase App Name if I'm not using Firebase Hosting? 134 | You may not use Firebase as your backend, but Hacker News does. The base HN API is backed by the Firebase Database. This library uses the Firebase Node SDK to retrieve data and coalesce it into a single UI friendly response. 135 | 136 | ### Contribute!? 137 | ```bash 138 | git clone https://github.com/davideast/hnpwa-api/ 139 | npm i 140 | npm run build # single build of the project 141 | npm run watch # typescript (tsc) watcher 142 | npm run serve # local node debug server 143 | npm run pack # local tarball for test installations 144 | ``` 145 | -------------------------------------------------------------------------------- /assets/rocket-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/hnpwa-api/9536d3bae37ced93e6ac613f6d2ec72c65cc8e15/assets/rocket-orange.png -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "public", 4 | "rewrites": [ 5 | { 6 | "source": "/v0**", 7 | "function": "api0" 8 | }, 9 | { 10 | "source": "/v0/**", 11 | "function": "api0" 12 | } 13 | ] 14 | }, 15 | "functions": { 16 | "runtime": "nodejs10" 17 | }, 18 | "emulators": { 19 | "functions": { 20 | "port": 5001 21 | }, 22 | "hosting": { 23 | "port": 5000 24 | }, 25 | "ui": { 26 | "enabled": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | const hnapi = require('hnpwa-api'); 2 | exports.api0 = hnapi.trigger({ 3 | useCors: true, 4 | routerPath: '/v0', 5 | cdnCacheExpiry: 1200, 6 | browserCacheExpiry: 300, 7 | runWith: { 8 | timeoutSeconds: 300, 9 | memory: '1GB' 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /functions/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "requires": true, 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@firebase/analytics": { 7 | "version": "0.5.0", 8 | "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.5.0.tgz", 9 | "integrity": "sha512-WyQ8BT6JSoXpg4q7SV9Yg5EPXbGbG8FkkXAIhV/AnslCglhpxegO1FU33qbuT4Grzc525hZJA97oqtQS8tm4Wg==", 10 | "requires": { 11 | "@firebase/analytics-types": "0.4.0", 12 | "@firebase/component": "0.1.19", 13 | "@firebase/installations": "0.4.17", 14 | "@firebase/logger": "0.2.6", 15 | "@firebase/util": "0.3.2", 16 | "tslib": "^1.11.1" 17 | } 18 | }, 19 | "@firebase/analytics-types": { 20 | "version": "0.4.0", 21 | "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.4.0.tgz", 22 | "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==" 23 | }, 24 | "@firebase/app": { 25 | "version": "0.6.11", 26 | "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.11.tgz", 27 | "integrity": "sha512-FH++PaoyTzfTAVuJ0gITNYEIcjT5G+D0671La27MU8Vvr6MTko+5YUZ4xS9QItyotSeRF4rMJ1KR7G8LSyySiA==", 28 | "requires": { 29 | "@firebase/app-types": "0.6.1", 30 | "@firebase/component": "0.1.19", 31 | "@firebase/logger": "0.2.6", 32 | "@firebase/util": "0.3.2", 33 | "dom-storage": "2.1.0", 34 | "tslib": "^1.11.1", 35 | "xmlhttprequest": "1.8.0" 36 | } 37 | }, 38 | "@firebase/app-types": { 39 | "version": "0.6.1", 40 | "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", 41 | "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" 42 | }, 43 | "@firebase/auth": { 44 | "version": "0.14.9", 45 | "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.9.tgz", 46 | "integrity": "sha512-PxYa2r5qUEdheXTvqROFrMstK8W4uPiP7NVfp+2Bec+AjY5PxZapCx/YFDLkU0D7YBI82H74PtZrzdJZw7TJ4w==", 47 | "requires": { 48 | "@firebase/auth-types": "0.10.1" 49 | } 50 | }, 51 | "@firebase/auth-interop-types": { 52 | "version": "0.1.5", 53 | "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", 54 | "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" 55 | }, 56 | "@firebase/auth-types": { 57 | "version": "0.10.1", 58 | "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", 59 | "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==" 60 | }, 61 | "@firebase/component": { 62 | "version": "0.1.19", 63 | "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", 64 | "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", 65 | "requires": { 66 | "@firebase/util": "0.3.2", 67 | "tslib": "^1.11.1" 68 | }, 69 | "dependencies": { 70 | "@firebase/util": { 71 | "version": "0.3.2", 72 | "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", 73 | "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", 74 | "requires": { 75 | "tslib": "^1.11.1" 76 | } 77 | }, 78 | "tslib": { 79 | "version": "1.13.0", 80 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", 81 | "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" 82 | } 83 | } 84 | }, 85 | "@firebase/database": { 86 | "version": "0.6.13", 87 | "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", 88 | "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", 89 | "requires": { 90 | "@firebase/auth-interop-types": "0.1.5", 91 | "@firebase/component": "0.1.19", 92 | "@firebase/database-types": "0.5.2", 93 | "@firebase/logger": "0.2.6", 94 | "@firebase/util": "0.3.2", 95 | "faye-websocket": "0.11.3", 96 | "tslib": "^1.11.1" 97 | } 98 | }, 99 | "@firebase/database-types": { 100 | "version": "0.5.2", 101 | "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", 102 | "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", 103 | "requires": { 104 | "@firebase/app-types": "0.6.1" 105 | } 106 | }, 107 | "@firebase/firestore": { 108 | "version": "1.17.2", 109 | "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.17.2.tgz", 110 | "integrity": "sha512-B0x0/AlKz4VkIkRW7vzPQvl4FGi2ClCDrTDCh3W5kryuIEMPLdyboglqA8JtaOGklcRYO8gpRwZGmw0EVzmyVg==", 111 | "requires": { 112 | "@firebase/component": "0.1.19", 113 | "@firebase/firestore-types": "1.13.0", 114 | "@firebase/logger": "0.2.6", 115 | "@firebase/util": "0.3.2", 116 | "@firebase/webchannel-wrapper": "0.3.0", 117 | "@grpc/grpc-js": "^1.0.0", 118 | "@grpc/proto-loader": "^0.5.0", 119 | "node-fetch": "2.6.1", 120 | "tslib": "^1.11.1" 121 | } 122 | }, 123 | "@firebase/firestore-types": { 124 | "version": "1.13.0", 125 | "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.13.0.tgz", 126 | "integrity": "sha512-QF5CAuYOHE6Zbsn1uEg6wkl836iP+i6C0C/Zs3kF60eebxZvTWp8JSZk19Ar+jj4w+ye8/7H5olu5CqDNjWpEA==" 127 | }, 128 | "@firebase/functions": { 129 | "version": "0.5.0", 130 | "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.5.0.tgz", 131 | "integrity": "sha512-j7/HVJR8uMlnTgavF8W1Vq+8IRJstxEtSVzK1yUhng7OParUCR+b3uMbMk7pcE+oQdeYTPtRoDf4deggTFws+A==", 132 | "requires": { 133 | "@firebase/component": "0.1.19", 134 | "@firebase/functions-types": "0.3.17", 135 | "@firebase/messaging-types": "0.5.0", 136 | "node-fetch": "2.6.1", 137 | "tslib": "^1.11.1" 138 | } 139 | }, 140 | "@firebase/functions-types": { 141 | "version": "0.3.17", 142 | "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", 143 | "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==" 144 | }, 145 | "@firebase/installations": { 146 | "version": "0.4.17", 147 | "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.17.tgz", 148 | "integrity": "sha512-AE/TyzIpwkC4UayRJD419xTqZkKzxwk0FLht3Dci8WI2OEKHSwoZG9xv4hOBZebe+fDzoV2EzfatQY8c/6Avig==", 149 | "requires": { 150 | "@firebase/component": "0.1.19", 151 | "@firebase/installations-types": "0.3.4", 152 | "@firebase/util": "0.3.2", 153 | "idb": "3.0.2", 154 | "tslib": "^1.11.1" 155 | } 156 | }, 157 | "@firebase/installations-types": { 158 | "version": "0.3.4", 159 | "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", 160 | "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==" 161 | }, 162 | "@firebase/logger": { 163 | "version": "0.2.6", 164 | "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", 165 | "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" 166 | }, 167 | "@firebase/messaging": { 168 | "version": "0.7.1", 169 | "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.7.1.tgz", 170 | "integrity": "sha512-iev/ST9v0xd/8YpGYrZtDcqdD9J6ZWzSuceRn8EKy5vIgQvW/rk2eTQc8axzvDpQ36ZfphMYuhW6XuNrR3Pd2Q==", 171 | "requires": { 172 | "@firebase/component": "0.1.19", 173 | "@firebase/installations": "0.4.17", 174 | "@firebase/messaging-types": "0.5.0", 175 | "@firebase/util": "0.3.2", 176 | "idb": "3.0.2", 177 | "tslib": "^1.11.1" 178 | } 179 | }, 180 | "@firebase/messaging-types": { 181 | "version": "0.5.0", 182 | "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.5.0.tgz", 183 | "integrity": "sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==" 184 | }, 185 | "@firebase/performance": { 186 | "version": "0.4.1", 187 | "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.4.1.tgz", 188 | "integrity": "sha512-eAqS3/456xnUwuTg4w58x2fYbvTtQpgt67lpBUX3DuhOqwiM8+JELRte52nDgum2lTaTZWiu5de9mPuAYx2WDg==", 189 | "requires": { 190 | "@firebase/component": "0.1.19", 191 | "@firebase/installations": "0.4.17", 192 | "@firebase/logger": "0.2.6", 193 | "@firebase/performance-types": "0.0.13", 194 | "@firebase/util": "0.3.2", 195 | "tslib": "^1.11.1" 196 | } 197 | }, 198 | "@firebase/performance-types": { 199 | "version": "0.0.13", 200 | "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", 201 | "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==" 202 | }, 203 | "@firebase/polyfill": { 204 | "version": "0.3.36", 205 | "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", 206 | "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", 207 | "requires": { 208 | "core-js": "3.6.5", 209 | "promise-polyfill": "8.1.3", 210 | "whatwg-fetch": "2.0.4" 211 | } 212 | }, 213 | "@firebase/remote-config": { 214 | "version": "0.1.28", 215 | "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.28.tgz", 216 | "integrity": "sha512-4zSdyxpt94jAnFhO8toNjG8oMKBD+xTuBIcK+Nw8BdQWeJhEamgXlupdBARUk1uf3AvYICngHH32+Si/dMVTbw==", 217 | "requires": { 218 | "@firebase/component": "0.1.19", 219 | "@firebase/installations": "0.4.17", 220 | "@firebase/logger": "0.2.6", 221 | "@firebase/remote-config-types": "0.1.9", 222 | "@firebase/util": "0.3.2", 223 | "tslib": "^1.11.1" 224 | } 225 | }, 226 | "@firebase/remote-config-types": { 227 | "version": "0.1.9", 228 | "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", 229 | "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==" 230 | }, 231 | "@firebase/storage": { 232 | "version": "0.3.43", 233 | "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.43.tgz", 234 | "integrity": "sha512-Jp54jcuyimLxPhZHFVAhNbQmgTu3Sda7vXjXrNpPEhlvvMSq4yuZBR6RrZxe/OrNVprLHh/6lTCjwjOVSo3bWA==", 235 | "requires": { 236 | "@firebase/component": "0.1.19", 237 | "@firebase/storage-types": "0.3.13", 238 | "@firebase/util": "0.3.2", 239 | "tslib": "^1.11.1" 240 | } 241 | }, 242 | "@firebase/storage-types": { 243 | "version": "0.3.13", 244 | "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", 245 | "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==" 246 | }, 247 | "@firebase/util": { 248 | "version": "0.3.2", 249 | "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", 250 | "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", 251 | "requires": { 252 | "tslib": "^1.11.1" 253 | } 254 | }, 255 | "@firebase/webchannel-wrapper": { 256 | "version": "0.3.0", 257 | "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.3.0.tgz", 258 | "integrity": "sha512-VniCGPIgSGNEgOkh5phb3iKmSGIzcwrccy3IomMFRWPCMiCk2y98UQNJEoDs1yIHtZMstVjYWKYxnunIGzC5UQ==" 259 | }, 260 | "@google-cloud/common": { 261 | "version": "3.4.0", 262 | "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.4.0.tgz", 263 | "integrity": "sha512-bVMQlK4aZEeopo2oJwDUJiBhPVjRRQHfFCCv9JowmKS3L//PBHNDJzC/LxJixGZEU3fh3YXkUwm67JZ5TBCCNQ==", 264 | "optional": true, 265 | "requires": { 266 | "@google-cloud/projectify": "^2.0.0", 267 | "@google-cloud/promisify": "^2.0.0", 268 | "arrify": "^2.0.1", 269 | "duplexify": "^4.1.1", 270 | "ent": "^2.2.0", 271 | "extend": "^3.0.2", 272 | "google-auth-library": "^6.0.0", 273 | "retry-request": "^4.1.1", 274 | "teeny-request": "^7.0.0" 275 | } 276 | }, 277 | "@google-cloud/firestore": { 278 | "version": "4.4.0", 279 | "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.4.0.tgz", 280 | "integrity": "sha512-nixsumd4C7eL+hHEgyihspzhBBNe3agsvNFRX0xfqO3uR/6ro4CUj9XdcCvdnSSd3yTyqKfdBSRK2fEj1jIbYg==", 281 | "optional": true, 282 | "requires": { 283 | "fast-deep-equal": "^3.1.1", 284 | "functional-red-black-tree": "^1.0.1", 285 | "google-gax": "^2.2.0" 286 | } 287 | }, 288 | "@google-cloud/paginator": { 289 | "version": "3.0.5", 290 | "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", 291 | "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==", 292 | "optional": true, 293 | "requires": { 294 | "arrify": "^2.0.0", 295 | "extend": "^3.0.2" 296 | }, 297 | "dependencies": { 298 | "arrify": { 299 | "version": "2.0.1", 300 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", 301 | "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", 302 | "optional": true 303 | } 304 | } 305 | }, 306 | "@google-cloud/projectify": { 307 | "version": "2.0.1", 308 | "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", 309 | "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==", 310 | "optional": true 311 | }, 312 | "@google-cloud/promisify": { 313 | "version": "2.0.3", 314 | "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", 315 | "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==", 316 | "optional": true 317 | }, 318 | "@google-cloud/storage": { 319 | "version": "5.3.0", 320 | "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.3.0.tgz", 321 | "integrity": "sha512-3t5UF3SZ14Bw2kcBHubCai6EIugU2GnQOstYWVSFuoO8IJ94RAaIOPq/dtexvQbUTpBTAGpd5smVR9WPL1mJVw==", 322 | "optional": true, 323 | "requires": { 324 | "@google-cloud/common": "^3.3.0", 325 | "@google-cloud/paginator": "^3.0.0", 326 | "@google-cloud/promisify": "^2.0.0", 327 | "arrify": "^2.0.0", 328 | "compressible": "^2.0.12", 329 | "concat-stream": "^2.0.0", 330 | "date-and-time": "^0.14.0", 331 | "duplexify": "^3.5.0", 332 | "extend": "^3.0.2", 333 | "gaxios": "^3.0.0", 334 | "gcs-resumable-upload": "^3.1.0", 335 | "hash-stream-validation": "^0.2.2", 336 | "mime": "^2.2.0", 337 | "mime-types": "^2.0.8", 338 | "onetime": "^5.1.0", 339 | "p-limit": "^3.0.1", 340 | "pumpify": "^2.0.0", 341 | "snakeize": "^0.1.0", 342 | "stream-events": "^1.0.1", 343 | "xdg-basedir": "^4.0.0" 344 | }, 345 | "dependencies": { 346 | "duplexify": { 347 | "version": "3.7.1", 348 | "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", 349 | "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", 350 | "optional": true, 351 | "requires": { 352 | "end-of-stream": "^1.0.0", 353 | "inherits": "^2.0.1", 354 | "readable-stream": "^2.0.0", 355 | "stream-shift": "^1.0.0" 356 | } 357 | }, 358 | "readable-stream": { 359 | "version": "2.3.7", 360 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 361 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 362 | "optional": true, 363 | "requires": { 364 | "core-util-is": "~1.0.0", 365 | "inherits": "~2.0.3", 366 | "isarray": "~1.0.0", 367 | "process-nextick-args": "~2.0.0", 368 | "safe-buffer": "~5.1.1", 369 | "string_decoder": "~1.1.1", 370 | "util-deprecate": "~1.0.1" 371 | } 372 | }, 373 | "safe-buffer": { 374 | "version": "5.1.2", 375 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 376 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 377 | "optional": true 378 | } 379 | } 380 | }, 381 | "@grpc/grpc-js": { 382 | "version": "1.1.7", 383 | "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.7.tgz", 384 | "integrity": "sha512-EuxMstI0u778dp0nk6Fe3gHXYPeV6FYsWOe0/QFwxv1NQ6bc5Wl/0Yxa4xl9uBlKElL6AIxuASmSfu7KEJhqiw==", 385 | "requires": { 386 | "@grpc/proto-loader": "^0.6.0-pre14", 387 | "@types/node": "^12.12.47", 388 | "google-auth-library": "^6.0.0", 389 | "semver": "^6.2.0" 390 | }, 391 | "dependencies": { 392 | "@grpc/proto-loader": { 393 | "version": "0.6.0-pre9", 394 | "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz", 395 | "integrity": "sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww==", 396 | "requires": { 397 | "@types/long": "^4.0.1", 398 | "lodash.camelcase": "^4.3.0", 399 | "long": "^4.0.0", 400 | "protobufjs": "^6.9.0", 401 | "yargs": "^15.3.1" 402 | } 403 | }, 404 | "@types/node": { 405 | "version": "12.12.62", 406 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.62.tgz", 407 | "integrity": "sha512-qAfo81CsD7yQIM9mVyh6B/U47li5g7cfpVQEDMfQeF8pSZVwzbhwU3crc0qG4DmpsebpJPR49AKOExQyJ05Cpg==" 408 | }, 409 | "agent-base": { 410 | "version": "6.0.1", 411 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", 412 | "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", 413 | "requires": { 414 | "debug": "4" 415 | } 416 | }, 417 | "ansi-regex": { 418 | "version": "5.0.0", 419 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 420 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 421 | }, 422 | "arrify": { 423 | "version": "2.0.1", 424 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", 425 | "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" 426 | }, 427 | "cliui": { 428 | "version": "6.0.0", 429 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", 430 | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", 431 | "requires": { 432 | "string-width": "^4.2.0", 433 | "strip-ansi": "^6.0.0", 434 | "wrap-ansi": "^6.2.0" 435 | } 436 | }, 437 | "debug": { 438 | "version": "4.2.0", 439 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 440 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 441 | "requires": { 442 | "ms": "2.1.2" 443 | } 444 | }, 445 | "find-up": { 446 | "version": "4.1.0", 447 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 448 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 449 | "requires": { 450 | "locate-path": "^5.0.0", 451 | "path-exists": "^4.0.0" 452 | } 453 | }, 454 | "gaxios": { 455 | "version": "3.2.0", 456 | "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", 457 | "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", 458 | "requires": { 459 | "abort-controller": "^3.0.0", 460 | "extend": "^3.0.2", 461 | "https-proxy-agent": "^5.0.0", 462 | "is-stream": "^2.0.0", 463 | "node-fetch": "^2.3.0" 464 | } 465 | }, 466 | "gcp-metadata": { 467 | "version": "4.2.0", 468 | "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.0.tgz", 469 | "integrity": "sha512-vQZD57cQkqIA6YPGXM/zc+PIZfNRFdukWGsGZ5+LcJzesi5xp6Gn7a02wRJi4eXPyArNMIYpPET4QMxGqtlk6Q==", 470 | "requires": { 471 | "gaxios": "^3.0.0", 472 | "json-bigint": "^1.0.0" 473 | } 474 | }, 475 | "google-auth-library": { 476 | "version": "6.1.0", 477 | "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.0.tgz", 478 | "integrity": "sha512-GbalszIADE1YPWhUyfFMrkLhFHnlAgoRcqGVW+MsLDPsuaOB5MRPk7NNafPDv9SherNE4EKzcYuxMJjaxzXMOw==", 479 | "requires": { 480 | "arrify": "^2.0.0", 481 | "base64-js": "^1.3.0", 482 | "ecdsa-sig-formatter": "^1.0.11", 483 | "fast-text-encoding": "^1.0.0", 484 | "gaxios": "^3.0.0", 485 | "gcp-metadata": "^4.1.0", 486 | "gtoken": "^5.0.0", 487 | "jws": "^4.0.0", 488 | "lru-cache": "^6.0.0" 489 | } 490 | }, 491 | "google-p12-pem": { 492 | "version": "3.0.3", 493 | "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", 494 | "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", 495 | "requires": { 496 | "node-forge": "^0.10.0" 497 | } 498 | }, 499 | "gtoken": { 500 | "version": "5.0.3", 501 | "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.3.tgz", 502 | "integrity": "sha512-Nyd1wZCMRc2dj/mAD0LlfQLcAO06uKdpKJXvK85SGrF5+5+Bpfil9u/2aw35ltvEHjvl0h5FMKN5knEU+9JrOg==", 503 | "requires": { 504 | "gaxios": "^3.0.0", 505 | "google-p12-pem": "^3.0.0", 506 | "jws": "^4.0.0", 507 | "mime": "^2.2.0" 508 | } 509 | }, 510 | "https-proxy-agent": { 511 | "version": "5.0.0", 512 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 513 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 514 | "requires": { 515 | "agent-base": "6", 516 | "debug": "4" 517 | } 518 | }, 519 | "is-fullwidth-code-point": { 520 | "version": "3.0.0", 521 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 522 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 523 | }, 524 | "jwa": { 525 | "version": "2.0.0", 526 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", 527 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", 528 | "requires": { 529 | "buffer-equal-constant-time": "1.0.1", 530 | "ecdsa-sig-formatter": "1.0.11", 531 | "safe-buffer": "^5.0.1" 532 | } 533 | }, 534 | "jws": { 535 | "version": "4.0.0", 536 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", 537 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", 538 | "requires": { 539 | "jwa": "^2.0.0", 540 | "safe-buffer": "^5.0.1" 541 | } 542 | }, 543 | "locate-path": { 544 | "version": "5.0.0", 545 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 546 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 547 | "requires": { 548 | "p-locate": "^4.1.0" 549 | } 550 | }, 551 | "lru-cache": { 552 | "version": "6.0.0", 553 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 554 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 555 | "requires": { 556 | "yallist": "^4.0.0" 557 | } 558 | }, 559 | "ms": { 560 | "version": "2.1.2", 561 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 562 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 563 | }, 564 | "node-forge": { 565 | "version": "0.10.0", 566 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", 567 | "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" 568 | }, 569 | "p-limit": { 570 | "version": "2.3.0", 571 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 572 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 573 | "requires": { 574 | "p-try": "^2.0.0" 575 | } 576 | }, 577 | "p-locate": { 578 | "version": "4.1.0", 579 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 580 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 581 | "requires": { 582 | "p-limit": "^2.2.0" 583 | } 584 | }, 585 | "p-try": { 586 | "version": "2.2.0", 587 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 588 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 589 | }, 590 | "path-exists": { 591 | "version": "4.0.0", 592 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 593 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 594 | }, 595 | "semver": { 596 | "version": "6.3.0", 597 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 598 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 599 | }, 600 | "string-width": { 601 | "version": "4.2.0", 602 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", 603 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", 604 | "requires": { 605 | "emoji-regex": "^8.0.0", 606 | "is-fullwidth-code-point": "^3.0.0", 607 | "strip-ansi": "^6.0.0" 608 | } 609 | }, 610 | "strip-ansi": { 611 | "version": "6.0.0", 612 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 613 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 614 | "requires": { 615 | "ansi-regex": "^5.0.0" 616 | } 617 | }, 618 | "wrap-ansi": { 619 | "version": "6.2.0", 620 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 621 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 622 | "requires": { 623 | "ansi-styles": "^4.0.0", 624 | "string-width": "^4.1.0", 625 | "strip-ansi": "^6.0.0" 626 | } 627 | }, 628 | "y18n": { 629 | "version": "4.0.0", 630 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 631 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" 632 | }, 633 | "yallist": { 634 | "version": "4.0.0", 635 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 636 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 637 | }, 638 | "yargs": { 639 | "version": "15.4.1", 640 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", 641 | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", 642 | "requires": { 643 | "cliui": "^6.0.0", 644 | "decamelize": "^1.2.0", 645 | "find-up": "^4.1.0", 646 | "get-caller-file": "^2.0.1", 647 | "require-directory": "^2.1.1", 648 | "require-main-filename": "^2.0.0", 649 | "set-blocking": "^2.0.0", 650 | "string-width": "^4.2.0", 651 | "which-module": "^2.0.0", 652 | "y18n": "^4.0.0", 653 | "yargs-parser": "^18.1.2" 654 | } 655 | } 656 | } 657 | }, 658 | "@grpc/proto-loader": { 659 | "version": "0.5.5", 660 | "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", 661 | "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", 662 | "requires": { 663 | "lodash.camelcase": "^4.3.0", 664 | "protobufjs": "^6.8.6" 665 | } 666 | }, 667 | "@protobufjs/aspromise": { 668 | "version": "1.1.2", 669 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", 670 | "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" 671 | }, 672 | "@protobufjs/base64": { 673 | "version": "1.1.2", 674 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", 675 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" 676 | }, 677 | "@protobufjs/codegen": { 678 | "version": "2.0.4", 679 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", 680 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" 681 | }, 682 | "@protobufjs/eventemitter": { 683 | "version": "1.1.0", 684 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", 685 | "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" 686 | }, 687 | "@protobufjs/fetch": { 688 | "version": "1.1.0", 689 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", 690 | "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", 691 | "requires": { 692 | "@protobufjs/aspromise": "^1.1.1", 693 | "@protobufjs/inquire": "^1.1.0" 694 | } 695 | }, 696 | "@protobufjs/float": { 697 | "version": "1.0.2", 698 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", 699 | "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" 700 | }, 701 | "@protobufjs/inquire": { 702 | "version": "1.1.0", 703 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", 704 | "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" 705 | }, 706 | "@protobufjs/path": { 707 | "version": "1.1.2", 708 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", 709 | "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" 710 | }, 711 | "@protobufjs/pool": { 712 | "version": "1.1.0", 713 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", 714 | "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" 715 | }, 716 | "@protobufjs/utf8": { 717 | "version": "1.1.0", 718 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", 719 | "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" 720 | }, 721 | "@tootallnate/once": { 722 | "version": "1.1.2", 723 | "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", 724 | "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", 725 | "optional": true 726 | }, 727 | "@types/body-parser": { 728 | "version": "1.19.0", 729 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", 730 | "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", 731 | "requires": { 732 | "@types/connect": "*", 733 | "@types/node": "*" 734 | } 735 | }, 736 | "@types/compression": { 737 | "version": "1.7.0", 738 | "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.0.tgz", 739 | "integrity": "sha512-3LzWUM+3k3XdWOUk/RO+uSjv7YWOatYq2QADJntK1pjkk4DfVP0KrIEPDnXRJxAAGKe0VpIPRmlINLDuCedZWw==", 740 | "requires": { 741 | "@types/express": "*" 742 | } 743 | }, 744 | "@types/connect": { 745 | "version": "3.4.33", 746 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", 747 | "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", 748 | "requires": { 749 | "@types/node": "*" 750 | } 751 | }, 752 | "@types/cors": { 753 | "version": "2.8.7", 754 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.7.tgz", 755 | "integrity": "sha512-sOdDRU3oRS7LBNTIqwDkPJyq0lpHYcbMTt0TrjzsXbk/e37hcLTH6eZX7CdbDeN0yJJvzw9hFBZkbtCSbk/jAQ==", 756 | "requires": { 757 | "@types/express": "*" 758 | } 759 | }, 760 | "@types/cron": { 761 | "version": "1.7.2", 762 | "resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.2.tgz", 763 | "integrity": "sha512-AEpNLRcsVSc5AdseJKNHpz0d4e8+ow+abTaC0fKDbAU86rF1evoFF0oC2fV9FdqtfVXkG2LKshpLTJCFOpyvTg==", 764 | "requires": { 765 | "@types/node": "*", 766 | "moment": ">=2.14.0" 767 | } 768 | }, 769 | "@types/express": { 770 | "version": "4.17.8", 771 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", 772 | "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", 773 | "requires": { 774 | "@types/body-parser": "*", 775 | "@types/express-serve-static-core": "*", 776 | "@types/qs": "*", 777 | "@types/serve-static": "*" 778 | } 779 | }, 780 | "@types/express-serve-static-core": { 781 | "version": "4.17.13", 782 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz", 783 | "integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==", 784 | "requires": { 785 | "@types/node": "*", 786 | "@types/qs": "*", 787 | "@types/range-parser": "*" 788 | } 789 | }, 790 | "@types/fs-extra": { 791 | "version": "9.0.1", 792 | "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz", 793 | "integrity": "sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg==", 794 | "requires": { 795 | "@types/node": "*" 796 | } 797 | }, 798 | "@types/long": { 799 | "version": "4.0.1", 800 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", 801 | "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" 802 | }, 803 | "@types/mime": { 804 | "version": "2.0.3", 805 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", 806 | "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" 807 | }, 808 | "@types/node": { 809 | "version": "8.10.64", 810 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.64.tgz", 811 | "integrity": "sha512-/EwBIb+imu8Qi/A3NF9sJ9iuKo7yV+pryqjmeRqaU0C4wBAOhas5mdvoYeJ5PCKrh6thRSJHdoasFqh3BQGILA==" 812 | }, 813 | "@types/qs": { 814 | "version": "6.9.5", 815 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", 816 | "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==" 817 | }, 818 | "@types/range-parser": { 819 | "version": "1.2.3", 820 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", 821 | "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" 822 | }, 823 | "@types/serve-static": { 824 | "version": "1.13.5", 825 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", 826 | "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", 827 | "requires": { 828 | "@types/express-serve-static-core": "*", 829 | "@types/mime": "*" 830 | } 831 | }, 832 | "@types/yargs": { 833 | "version": "15.0.7", 834 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz", 835 | "integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==", 836 | "requires": { 837 | "@types/yargs-parser": "*" 838 | } 839 | }, 840 | "@types/yargs-parser": { 841 | "version": "15.0.0", 842 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", 843 | "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" 844 | }, 845 | "abort-controller": { 846 | "version": "3.0.0", 847 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 848 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 849 | "requires": { 850 | "event-target-shim": "^5.0.0" 851 | } 852 | }, 853 | "accepts": { 854 | "version": "1.3.7", 855 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 856 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 857 | "requires": { 858 | "mime-types": "~2.1.24", 859 | "negotiator": "0.6.2" 860 | } 861 | }, 862 | "agent-base": { 863 | "version": "6.0.1", 864 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", 865 | "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", 866 | "optional": true, 867 | "requires": { 868 | "debug": "4" 869 | } 870 | }, 871 | "ansi-regex": { 872 | "version": "5.0.0", 873 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 874 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 875 | }, 876 | "ansi-styles": { 877 | "version": "4.3.0", 878 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 879 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 880 | "requires": { 881 | "color-convert": "^2.0.1" 882 | } 883 | }, 884 | "array-flatten": { 885 | "version": "1.1.1", 886 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 887 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 888 | }, 889 | "arrify": { 890 | "version": "2.0.1", 891 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", 892 | "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", 893 | "optional": true 894 | }, 895 | "at-least-node": { 896 | "version": "1.0.0", 897 | "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", 898 | "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" 899 | }, 900 | "base64-js": { 901 | "version": "1.3.1", 902 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 903 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" 904 | }, 905 | "bignumber.js": { 906 | "version": "9.0.1", 907 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", 908 | "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" 909 | }, 910 | "body-parser": { 911 | "version": "1.19.0", 912 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 913 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 914 | "requires": { 915 | "bytes": "3.1.0", 916 | "content-type": "~1.0.4", 917 | "debug": "2.6.9", 918 | "depd": "~1.1.2", 919 | "http-errors": "1.7.2", 920 | "iconv-lite": "0.4.24", 921 | "on-finished": "~2.3.0", 922 | "qs": "6.7.0", 923 | "raw-body": "2.4.0", 924 | "type-is": "~1.6.17" 925 | }, 926 | "dependencies": { 927 | "debug": { 928 | "version": "2.6.9", 929 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 930 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 931 | "requires": { 932 | "ms": "2.0.0" 933 | } 934 | }, 935 | "qs": { 936 | "version": "6.7.0", 937 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 938 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 939 | } 940 | } 941 | }, 942 | "buffer-equal-constant-time": { 943 | "version": "1.0.1", 944 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 945 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 946 | }, 947 | "buffer-from": { 948 | "version": "1.1.1", 949 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 950 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 951 | "optional": true 952 | }, 953 | "bytes": { 954 | "version": "3.1.0", 955 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 956 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 957 | }, 958 | "cliui": { 959 | "version": "7.0.1", 960 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.1.tgz", 961 | "integrity": "sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ==", 962 | "requires": { 963 | "string-width": "^4.2.0", 964 | "strip-ansi": "^6.0.0", 965 | "wrap-ansi": "^7.0.0" 966 | } 967 | }, 968 | "color-convert": { 969 | "version": "2.0.1", 970 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 971 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 972 | "requires": { 973 | "color-name": "~1.1.4" 974 | } 975 | }, 976 | "color-name": { 977 | "version": "1.1.4", 978 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 979 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 980 | }, 981 | "compressible": { 982 | "version": "2.0.18", 983 | "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", 984 | "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", 985 | "requires": { 986 | "mime-db": ">= 1.43.0 < 2" 987 | } 988 | }, 989 | "compression": { 990 | "version": "1.7.4", 991 | "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", 992 | "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", 993 | "requires": { 994 | "accepts": "~1.3.5", 995 | "bytes": "3.0.0", 996 | "compressible": "~2.0.16", 997 | "debug": "2.6.9", 998 | "on-headers": "~1.0.2", 999 | "safe-buffer": "5.1.2", 1000 | "vary": "~1.1.2" 1001 | }, 1002 | "dependencies": { 1003 | "bytes": { 1004 | "version": "3.0.0", 1005 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 1006 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 1007 | }, 1008 | "debug": { 1009 | "version": "2.6.9", 1010 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1011 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1012 | "requires": { 1013 | "ms": "2.0.0" 1014 | } 1015 | }, 1016 | "safe-buffer": { 1017 | "version": "5.1.2", 1018 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1019 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1020 | } 1021 | } 1022 | }, 1023 | "concat-stream": { 1024 | "version": "2.0.0", 1025 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", 1026 | "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", 1027 | "optional": true, 1028 | "requires": { 1029 | "buffer-from": "^1.0.0", 1030 | "inherits": "^2.0.3", 1031 | "readable-stream": "^3.0.2", 1032 | "typedarray": "^0.0.6" 1033 | } 1034 | }, 1035 | "configstore": { 1036 | "version": "5.0.1", 1037 | "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", 1038 | "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", 1039 | "optional": true, 1040 | "requires": { 1041 | "dot-prop": "^5.2.0", 1042 | "graceful-fs": "^4.1.2", 1043 | "make-dir": "^3.0.0", 1044 | "unique-string": "^2.0.0", 1045 | "write-file-atomic": "^3.0.0", 1046 | "xdg-basedir": "^4.0.0" 1047 | } 1048 | }, 1049 | "content-disposition": { 1050 | "version": "0.5.3", 1051 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 1052 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 1053 | "requires": { 1054 | "safe-buffer": "5.1.2" 1055 | }, 1056 | "dependencies": { 1057 | "safe-buffer": { 1058 | "version": "5.1.2", 1059 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1060 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1061 | } 1062 | } 1063 | }, 1064 | "content-type": { 1065 | "version": "1.0.4", 1066 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 1067 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 1068 | }, 1069 | "cookie": { 1070 | "version": "0.4.0", 1071 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 1072 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 1073 | }, 1074 | "cookie-signature": { 1075 | "version": "1.0.6", 1076 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 1077 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 1078 | }, 1079 | "core-js": { 1080 | "version": "3.6.5", 1081 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", 1082 | "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" 1083 | }, 1084 | "core-util-is": { 1085 | "version": "1.0.2", 1086 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 1087 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 1088 | "optional": true 1089 | }, 1090 | "cors": { 1091 | "version": "2.8.5", 1092 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 1093 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 1094 | "requires": { 1095 | "object-assign": "^4", 1096 | "vary": "^1" 1097 | } 1098 | }, 1099 | "cron": { 1100 | "version": "1.8.2", 1101 | "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", 1102 | "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", 1103 | "requires": { 1104 | "moment-timezone": "^0.5.x" 1105 | } 1106 | }, 1107 | "crypto-random-string": { 1108 | "version": "2.0.0", 1109 | "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", 1110 | "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", 1111 | "optional": true 1112 | }, 1113 | "date-and-time": { 1114 | "version": "0.14.1", 1115 | "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.1.tgz", 1116 | "integrity": "sha512-M4RggEH5OF2ZuCOxgOU67R6Z9ohjKbxGvAQz48vj53wLmL0bAgumkBvycR32f30pK+Og9pIR+RFDyChbaE4oLA==", 1117 | "optional": true 1118 | }, 1119 | "debug": { 1120 | "version": "4.2.0", 1121 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 1122 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 1123 | "optional": true, 1124 | "requires": { 1125 | "ms": "2.1.2" 1126 | }, 1127 | "dependencies": { 1128 | "ms": { 1129 | "version": "2.1.2", 1130 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1131 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1132 | "optional": true 1133 | } 1134 | } 1135 | }, 1136 | "decamelize": { 1137 | "version": "1.2.0", 1138 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 1139 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 1140 | }, 1141 | "depd": { 1142 | "version": "1.1.2", 1143 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 1144 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 1145 | }, 1146 | "destroy": { 1147 | "version": "1.0.4", 1148 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 1149 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 1150 | }, 1151 | "dicer": { 1152 | "version": "0.3.0", 1153 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", 1154 | "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", 1155 | "requires": { 1156 | "streamsearch": "0.1.2" 1157 | } 1158 | }, 1159 | "dom-storage": { 1160 | "version": "2.1.0", 1161 | "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", 1162 | "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==" 1163 | }, 1164 | "dot-prop": { 1165 | "version": "5.3.0", 1166 | "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", 1167 | "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", 1168 | "optional": true, 1169 | "requires": { 1170 | "is-obj": "^2.0.0" 1171 | } 1172 | }, 1173 | "duplexify": { 1174 | "version": "4.1.1", 1175 | "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", 1176 | "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", 1177 | "optional": true, 1178 | "requires": { 1179 | "end-of-stream": "^1.4.1", 1180 | "inherits": "^2.0.3", 1181 | "readable-stream": "^3.1.1", 1182 | "stream-shift": "^1.0.0" 1183 | } 1184 | }, 1185 | "ecdsa-sig-formatter": { 1186 | "version": "1.0.11", 1187 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 1188 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 1189 | "requires": { 1190 | "safe-buffer": "^5.0.1" 1191 | } 1192 | }, 1193 | "ee-first": { 1194 | "version": "1.1.1", 1195 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1196 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 1197 | }, 1198 | "emoji-regex": { 1199 | "version": "8.0.0", 1200 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1201 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 1202 | }, 1203 | "encodeurl": { 1204 | "version": "1.0.2", 1205 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1206 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 1207 | }, 1208 | "end-of-stream": { 1209 | "version": "1.4.4", 1210 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 1211 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 1212 | "optional": true, 1213 | "requires": { 1214 | "once": "^1.4.0" 1215 | } 1216 | }, 1217 | "ent": { 1218 | "version": "2.2.0", 1219 | "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", 1220 | "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", 1221 | "optional": true 1222 | }, 1223 | "escalade": { 1224 | "version": "3.1.0", 1225 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", 1226 | "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==" 1227 | }, 1228 | "escape-html": { 1229 | "version": "1.0.3", 1230 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1231 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 1232 | }, 1233 | "etag": { 1234 | "version": "1.8.1", 1235 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1236 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 1237 | }, 1238 | "event-target-shim": { 1239 | "version": "5.0.1", 1240 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 1241 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 1242 | }, 1243 | "express": { 1244 | "version": "4.17.1", 1245 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 1246 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 1247 | "requires": { 1248 | "accepts": "~1.3.7", 1249 | "array-flatten": "1.1.1", 1250 | "body-parser": "1.19.0", 1251 | "content-disposition": "0.5.3", 1252 | "content-type": "~1.0.4", 1253 | "cookie": "0.4.0", 1254 | "cookie-signature": "1.0.6", 1255 | "debug": "2.6.9", 1256 | "depd": "~1.1.2", 1257 | "encodeurl": "~1.0.2", 1258 | "escape-html": "~1.0.3", 1259 | "etag": "~1.8.1", 1260 | "finalhandler": "~1.1.2", 1261 | "fresh": "0.5.2", 1262 | "merge-descriptors": "1.0.1", 1263 | "methods": "~1.1.2", 1264 | "on-finished": "~2.3.0", 1265 | "parseurl": "~1.3.3", 1266 | "path-to-regexp": "0.1.7", 1267 | "proxy-addr": "~2.0.5", 1268 | "qs": "6.7.0", 1269 | "range-parser": "~1.2.1", 1270 | "safe-buffer": "5.1.2", 1271 | "send": "0.17.1", 1272 | "serve-static": "1.14.1", 1273 | "setprototypeof": "1.1.1", 1274 | "statuses": "~1.5.0", 1275 | "type-is": "~1.6.18", 1276 | "utils-merge": "1.0.1", 1277 | "vary": "~1.1.2" 1278 | }, 1279 | "dependencies": { 1280 | "debug": { 1281 | "version": "2.6.9", 1282 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1283 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1284 | "requires": { 1285 | "ms": "2.0.0" 1286 | } 1287 | }, 1288 | "qs": { 1289 | "version": "6.7.0", 1290 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1291 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 1292 | }, 1293 | "safe-buffer": { 1294 | "version": "5.1.2", 1295 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1296 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1297 | } 1298 | } 1299 | }, 1300 | "extend": { 1301 | "version": "3.0.2", 1302 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 1303 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 1304 | }, 1305 | "fast-deep-equal": { 1306 | "version": "3.1.3", 1307 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1308 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1309 | "optional": true 1310 | }, 1311 | "fast-text-encoding": { 1312 | "version": "1.0.3", 1313 | "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", 1314 | "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" 1315 | }, 1316 | "faye-websocket": { 1317 | "version": "0.11.3", 1318 | "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", 1319 | "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", 1320 | "requires": { 1321 | "websocket-driver": ">=0.5.1" 1322 | } 1323 | }, 1324 | "finalhandler": { 1325 | "version": "1.1.2", 1326 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 1327 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 1328 | "requires": { 1329 | "debug": "2.6.9", 1330 | "encodeurl": "~1.0.2", 1331 | "escape-html": "~1.0.3", 1332 | "on-finished": "~2.3.0", 1333 | "parseurl": "~1.3.3", 1334 | "statuses": "~1.5.0", 1335 | "unpipe": "~1.0.0" 1336 | }, 1337 | "dependencies": { 1338 | "debug": { 1339 | "version": "2.6.9", 1340 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1341 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1342 | "requires": { 1343 | "ms": "2.0.0" 1344 | } 1345 | } 1346 | } 1347 | }, 1348 | "firebase": { 1349 | "version": "7.22.0", 1350 | "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.22.0.tgz", 1351 | "integrity": "sha512-DoE93JoTYppZc+vOB10HDpp49UHPaz4MklE5tJ2XGCZ8ejbU/MOUwOtG4jakAkrl/rZNKVQ6yEimVsQQtZY+5w==", 1352 | "requires": { 1353 | "@firebase/analytics": "0.5.0", 1354 | "@firebase/app": "0.6.11", 1355 | "@firebase/app-types": "0.6.1", 1356 | "@firebase/auth": "0.14.9", 1357 | "@firebase/database": "0.6.13", 1358 | "@firebase/firestore": "1.17.2", 1359 | "@firebase/functions": "0.5.0", 1360 | "@firebase/installations": "0.4.17", 1361 | "@firebase/messaging": "0.7.1", 1362 | "@firebase/performance": "0.4.1", 1363 | "@firebase/polyfill": "0.3.36", 1364 | "@firebase/remote-config": "0.1.28", 1365 | "@firebase/storage": "0.3.43", 1366 | "@firebase/util": "0.3.2" 1367 | } 1368 | }, 1369 | "firebase-admin": { 1370 | "version": "9.2.0", 1371 | "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.2.0.tgz", 1372 | "integrity": "sha512-LhnMYl71B4gP1FlTLfwaYlOWhBCAcNF+byb2CPTfaW/T4hkp4qlXOgo2bws/zbAv5X9GTFqGir3KexMslVGsIA==", 1373 | "requires": { 1374 | "@firebase/database": "^0.6.10", 1375 | "@firebase/database-types": "^0.5.2", 1376 | "@google-cloud/firestore": "^4.0.0", 1377 | "@google-cloud/storage": "^5.3.0", 1378 | "@types/node": "^10.10.0", 1379 | "dicer": "^0.3.0", 1380 | "jsonwebtoken": "^8.5.1", 1381 | "node-forge": "^0.10.0" 1382 | }, 1383 | "dependencies": { 1384 | "@types/node": { 1385 | "version": "10.17.35", 1386 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.35.tgz", 1387 | "integrity": "sha512-gXx7jAWpMddu0f7a+L+txMplp3FnHl53OhQIF9puXKq3hDGY/GjH+MF04oWnV/adPSCrbtHumDCFwzq2VhltWA==" 1388 | } 1389 | } 1390 | }, 1391 | "firebase-functions": { 1392 | "version": "3.11.0", 1393 | "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.11.0.tgz", 1394 | "integrity": "sha512-i1uMhZ/M6i5SCI3ulKo7EWX0/LD+I5o6N+sk0HbOWfzyWfOl0iJTvQkR3BVDcjrlhPVC4xG1bDTLxd+DTkLqaw==", 1395 | "requires": { 1396 | "@types/express": "4.17.3", 1397 | "cors": "^2.8.5", 1398 | "express": "^4.17.1", 1399 | "lodash": "^4.17.14" 1400 | }, 1401 | "dependencies": { 1402 | "@types/express": { 1403 | "version": "4.17.3", 1404 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", 1405 | "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", 1406 | "requires": { 1407 | "@types/body-parser": "*", 1408 | "@types/express-serve-static-core": "*", 1409 | "@types/serve-static": "*" 1410 | } 1411 | } 1412 | } 1413 | }, 1414 | "forwarded": { 1415 | "version": "0.1.2", 1416 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 1417 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 1418 | }, 1419 | "fresh": { 1420 | "version": "0.5.2", 1421 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1422 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 1423 | }, 1424 | "fs-extra": { 1425 | "version": "9.0.1", 1426 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", 1427 | "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", 1428 | "requires": { 1429 | "at-least-node": "^1.0.0", 1430 | "graceful-fs": "^4.2.0", 1431 | "jsonfile": "^6.0.1", 1432 | "universalify": "^1.0.0" 1433 | } 1434 | }, 1435 | "functional-red-black-tree": { 1436 | "version": "1.0.1", 1437 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 1438 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 1439 | "optional": true 1440 | }, 1441 | "gaxios": { 1442 | "version": "3.2.0", 1443 | "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", 1444 | "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", 1445 | "optional": true, 1446 | "requires": { 1447 | "abort-controller": "^3.0.0", 1448 | "extend": "^3.0.2", 1449 | "https-proxy-agent": "^5.0.0", 1450 | "is-stream": "^2.0.0", 1451 | "node-fetch": "^2.3.0" 1452 | } 1453 | }, 1454 | "gcp-metadata": { 1455 | "version": "4.2.0", 1456 | "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.0.tgz", 1457 | "integrity": "sha512-vQZD57cQkqIA6YPGXM/zc+PIZfNRFdukWGsGZ5+LcJzesi5xp6Gn7a02wRJi4eXPyArNMIYpPET4QMxGqtlk6Q==", 1458 | "optional": true, 1459 | "requires": { 1460 | "gaxios": "^3.0.0", 1461 | "json-bigint": "^1.0.0" 1462 | } 1463 | }, 1464 | "gcs-resumable-upload": { 1465 | "version": "3.1.1", 1466 | "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", 1467 | "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", 1468 | "optional": true, 1469 | "requires": { 1470 | "abort-controller": "^3.0.0", 1471 | "configstore": "^5.0.0", 1472 | "extend": "^3.0.2", 1473 | "gaxios": "^3.0.0", 1474 | "google-auth-library": "^6.0.0", 1475 | "pumpify": "^2.0.0", 1476 | "stream-events": "^1.0.4" 1477 | } 1478 | }, 1479 | "get-caller-file": { 1480 | "version": "2.0.5", 1481 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 1482 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 1483 | }, 1484 | "google-auth-library": { 1485 | "version": "6.1.0", 1486 | "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.0.tgz", 1487 | "integrity": "sha512-GbalszIADE1YPWhUyfFMrkLhFHnlAgoRcqGVW+MsLDPsuaOB5MRPk7NNafPDv9SherNE4EKzcYuxMJjaxzXMOw==", 1488 | "optional": true, 1489 | "requires": { 1490 | "arrify": "^2.0.0", 1491 | "base64-js": "^1.3.0", 1492 | "ecdsa-sig-formatter": "^1.0.11", 1493 | "fast-text-encoding": "^1.0.0", 1494 | "gaxios": "^3.0.0", 1495 | "gcp-metadata": "^4.1.0", 1496 | "gtoken": "^5.0.0", 1497 | "jws": "^4.0.0", 1498 | "lru-cache": "^6.0.0" 1499 | } 1500 | }, 1501 | "google-gax": { 1502 | "version": "2.9.0", 1503 | "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.9.0.tgz", 1504 | "integrity": "sha512-MFMwA7Fb8PEwjnYwfGXjZMidCNyMl3gSnvS/+kS8TQioJZQDpzK+W3dmwyNyig/U13+kbABqDnbkkAXJ5NiUkw==", 1505 | "optional": true, 1506 | "requires": { 1507 | "@grpc/grpc-js": "~1.1.1", 1508 | "@grpc/proto-loader": "^0.5.1", 1509 | "@types/long": "^4.0.0", 1510 | "abort-controller": "^3.0.0", 1511 | "duplexify": "^4.0.0", 1512 | "google-auth-library": "^6.0.0", 1513 | "is-stream-ended": "^0.1.4", 1514 | "node-fetch": "^2.6.1", 1515 | "protobufjs": "^6.9.0", 1516 | "retry-request": "^4.0.0" 1517 | } 1518 | }, 1519 | "google-p12-pem": { 1520 | "version": "3.0.3", 1521 | "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", 1522 | "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", 1523 | "optional": true, 1524 | "requires": { 1525 | "node-forge": "^0.10.0" 1526 | } 1527 | }, 1528 | "graceful-fs": { 1529 | "version": "4.2.4", 1530 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 1531 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" 1532 | }, 1533 | "gtoken": { 1534 | "version": "5.0.3", 1535 | "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.3.tgz", 1536 | "integrity": "sha512-Nyd1wZCMRc2dj/mAD0LlfQLcAO06uKdpKJXvK85SGrF5+5+Bpfil9u/2aw35ltvEHjvl0h5FMKN5knEU+9JrOg==", 1537 | "optional": true, 1538 | "requires": { 1539 | "gaxios": "^3.0.0", 1540 | "google-p12-pem": "^3.0.0", 1541 | "jws": "^4.0.0", 1542 | "mime": "^2.2.0" 1543 | } 1544 | }, 1545 | "hash-stream-validation": { 1546 | "version": "0.2.4", 1547 | "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", 1548 | "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", 1549 | "optional": true 1550 | }, 1551 | "hnpwa-api": { 1552 | "version": "0.2.3", 1553 | "resolved": "https://registry.npmjs.org/hnpwa-api/-/hnpwa-api-0.2.3.tgz", 1554 | "integrity": "sha512-y8gK6FUCvBpIq/LdgeAxQ5hhRKr9DLiEHFBvrTBBLY+GE9brJGRPpgndiowZGhk7IlRhZdK0o12il7Qy2RmWWQ==", 1555 | "requires": { 1556 | "@types/compression": "^1.7.0", 1557 | "@types/cors": "^2.8.7", 1558 | "@types/cron": "^1.7.2", 1559 | "@types/express": "^4.7.18", 1560 | "@types/fs-extra": "^9.0.1", 1561 | "@types/yargs": "^15.0.7", 1562 | "compression": "^1.7.4", 1563 | "cors": "^2.8.3", 1564 | "cron": "^1.3.0", 1565 | "express": "^4.15.3", 1566 | "firebase": "^7.0.0", 1567 | "firebase-admin": "^9.00.0", 1568 | "firebase-functions": "^3.0.0", 1569 | "fs-extra": "^9.0.0", 1570 | "moment": "^2.22.2", 1571 | "yargs": "^16.0.3" 1572 | } 1573 | }, 1574 | "http-errors": { 1575 | "version": "1.7.2", 1576 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 1577 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 1578 | "requires": { 1579 | "depd": "~1.1.2", 1580 | "inherits": "2.0.3", 1581 | "setprototypeof": "1.1.1", 1582 | "statuses": ">= 1.5.0 < 2", 1583 | "toidentifier": "1.0.0" 1584 | }, 1585 | "dependencies": { 1586 | "inherits": { 1587 | "version": "2.0.3", 1588 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1589 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 1590 | } 1591 | } 1592 | }, 1593 | "http-parser-js": { 1594 | "version": "0.5.2", 1595 | "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", 1596 | "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" 1597 | }, 1598 | "http-proxy-agent": { 1599 | "version": "4.0.1", 1600 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", 1601 | "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", 1602 | "optional": true, 1603 | "requires": { 1604 | "@tootallnate/once": "1", 1605 | "agent-base": "6", 1606 | "debug": "4" 1607 | }, 1608 | "dependencies": { 1609 | "agent-base": { 1610 | "version": "6.0.1", 1611 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", 1612 | "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", 1613 | "optional": true, 1614 | "requires": { 1615 | "debug": "4" 1616 | } 1617 | }, 1618 | "debug": { 1619 | "version": "4.2.0", 1620 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 1621 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 1622 | "optional": true, 1623 | "requires": { 1624 | "ms": "2.1.2" 1625 | } 1626 | }, 1627 | "ms": { 1628 | "version": "2.1.2", 1629 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1630 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1631 | "optional": true 1632 | } 1633 | } 1634 | }, 1635 | "https-proxy-agent": { 1636 | "version": "5.0.0", 1637 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 1638 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 1639 | "optional": true, 1640 | "requires": { 1641 | "agent-base": "6", 1642 | "debug": "4" 1643 | } 1644 | }, 1645 | "iconv-lite": { 1646 | "version": "0.4.24", 1647 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1648 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1649 | "requires": { 1650 | "safer-buffer": ">= 2.1.2 < 3" 1651 | } 1652 | }, 1653 | "idb": { 1654 | "version": "3.0.2", 1655 | "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", 1656 | "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" 1657 | }, 1658 | "imurmurhash": { 1659 | "version": "0.1.4", 1660 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1661 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 1662 | "optional": true 1663 | }, 1664 | "inherits": { 1665 | "version": "2.0.4", 1666 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1667 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1668 | "optional": true 1669 | }, 1670 | "ipaddr.js": { 1671 | "version": "1.9.1", 1672 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1673 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 1674 | }, 1675 | "is-fullwidth-code-point": { 1676 | "version": "3.0.0", 1677 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1678 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 1679 | }, 1680 | "is-obj": { 1681 | "version": "2.0.0", 1682 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", 1683 | "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", 1684 | "optional": true 1685 | }, 1686 | "is-stream": { 1687 | "version": "2.0.0", 1688 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", 1689 | "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" 1690 | }, 1691 | "is-stream-ended": { 1692 | "version": "0.1.4", 1693 | "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", 1694 | "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", 1695 | "optional": true 1696 | }, 1697 | "is-typedarray": { 1698 | "version": "1.0.0", 1699 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 1700 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", 1701 | "optional": true 1702 | }, 1703 | "isarray": { 1704 | "version": "1.0.0", 1705 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1706 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 1707 | "optional": true 1708 | }, 1709 | "json-bigint": { 1710 | "version": "1.0.0", 1711 | "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", 1712 | "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", 1713 | "requires": { 1714 | "bignumber.js": "^9.0.0" 1715 | } 1716 | }, 1717 | "jsonfile": { 1718 | "version": "6.0.1", 1719 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", 1720 | "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", 1721 | "requires": { 1722 | "graceful-fs": "^4.1.6", 1723 | "universalify": "^1.0.0" 1724 | } 1725 | }, 1726 | "jsonwebtoken": { 1727 | "version": "8.5.1", 1728 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 1729 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 1730 | "requires": { 1731 | "jws": "^3.2.2", 1732 | "lodash.includes": "^4.3.0", 1733 | "lodash.isboolean": "^3.0.3", 1734 | "lodash.isinteger": "^4.0.4", 1735 | "lodash.isnumber": "^3.0.3", 1736 | "lodash.isplainobject": "^4.0.6", 1737 | "lodash.isstring": "^4.0.1", 1738 | "lodash.once": "^4.0.0", 1739 | "ms": "^2.1.1", 1740 | "semver": "^5.6.0" 1741 | }, 1742 | "dependencies": { 1743 | "jwa": { 1744 | "version": "1.4.1", 1745 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 1746 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 1747 | "requires": { 1748 | "buffer-equal-constant-time": "1.0.1", 1749 | "ecdsa-sig-formatter": "1.0.11", 1750 | "safe-buffer": "^5.0.1" 1751 | } 1752 | }, 1753 | "jws": { 1754 | "version": "3.2.2", 1755 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 1756 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 1757 | "requires": { 1758 | "jwa": "^1.4.1", 1759 | "safe-buffer": "^5.0.1" 1760 | } 1761 | }, 1762 | "ms": { 1763 | "version": "2.1.2", 1764 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1765 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1766 | }, 1767 | "semver": { 1768 | "version": "5.7.1", 1769 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1770 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1771 | } 1772 | } 1773 | }, 1774 | "jwa": { 1775 | "version": "2.0.0", 1776 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", 1777 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", 1778 | "optional": true, 1779 | "requires": { 1780 | "buffer-equal-constant-time": "1.0.1", 1781 | "ecdsa-sig-formatter": "1.0.11", 1782 | "safe-buffer": "^5.0.1" 1783 | } 1784 | }, 1785 | "jws": { 1786 | "version": "4.0.0", 1787 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", 1788 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", 1789 | "optional": true, 1790 | "requires": { 1791 | "jwa": "^2.0.0", 1792 | "safe-buffer": "^5.0.1" 1793 | } 1794 | }, 1795 | "lodash": { 1796 | "version": "4.17.20", 1797 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", 1798 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" 1799 | }, 1800 | "lodash.camelcase": { 1801 | "version": "4.3.0", 1802 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 1803 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 1804 | }, 1805 | "lodash.includes": { 1806 | "version": "4.3.0", 1807 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 1808 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 1809 | }, 1810 | "lodash.isboolean": { 1811 | "version": "3.0.3", 1812 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 1813 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 1814 | }, 1815 | "lodash.isinteger": { 1816 | "version": "4.0.4", 1817 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 1818 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 1819 | }, 1820 | "lodash.isnumber": { 1821 | "version": "3.0.3", 1822 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 1823 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 1824 | }, 1825 | "lodash.isplainobject": { 1826 | "version": "4.0.6", 1827 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 1828 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 1829 | }, 1830 | "lodash.isstring": { 1831 | "version": "4.0.1", 1832 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 1833 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 1834 | }, 1835 | "lodash.once": { 1836 | "version": "4.1.1", 1837 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 1838 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 1839 | }, 1840 | "long": { 1841 | "version": "4.0.0", 1842 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 1843 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 1844 | }, 1845 | "lru-cache": { 1846 | "version": "6.0.0", 1847 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1848 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1849 | "optional": true, 1850 | "requires": { 1851 | "yallist": "^4.0.0" 1852 | } 1853 | }, 1854 | "make-dir": { 1855 | "version": "3.1.0", 1856 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 1857 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 1858 | "optional": true, 1859 | "requires": { 1860 | "semver": "^6.0.0" 1861 | } 1862 | }, 1863 | "media-typer": { 1864 | "version": "0.3.0", 1865 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1866 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 1867 | }, 1868 | "merge-descriptors": { 1869 | "version": "1.0.1", 1870 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1871 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 1872 | }, 1873 | "methods": { 1874 | "version": "1.1.2", 1875 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1876 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 1877 | }, 1878 | "mime": { 1879 | "version": "2.4.6", 1880 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", 1881 | "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" 1882 | }, 1883 | "mime-db": { 1884 | "version": "1.44.0", 1885 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 1886 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 1887 | }, 1888 | "mime-types": { 1889 | "version": "2.1.27", 1890 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 1891 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 1892 | "requires": { 1893 | "mime-db": "1.44.0" 1894 | } 1895 | }, 1896 | "mimic-fn": { 1897 | "version": "2.1.0", 1898 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 1899 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 1900 | "optional": true 1901 | }, 1902 | "moment": { 1903 | "version": "2.29.0", 1904 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz", 1905 | "integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA==" 1906 | }, 1907 | "moment-timezone": { 1908 | "version": "0.5.31", 1909 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", 1910 | "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", 1911 | "requires": { 1912 | "moment": ">= 2.9.0" 1913 | } 1914 | }, 1915 | "ms": { 1916 | "version": "2.0.0", 1917 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1918 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1919 | }, 1920 | "negotiator": { 1921 | "version": "0.6.2", 1922 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1923 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1924 | }, 1925 | "node-fetch": { 1926 | "version": "2.6.1", 1927 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 1928 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 1929 | }, 1930 | "node-forge": { 1931 | "version": "0.10.0", 1932 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", 1933 | "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" 1934 | }, 1935 | "object-assign": { 1936 | "version": "4.1.1", 1937 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1938 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1939 | }, 1940 | "on-finished": { 1941 | "version": "2.3.0", 1942 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1943 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1944 | "requires": { 1945 | "ee-first": "1.1.1" 1946 | } 1947 | }, 1948 | "on-headers": { 1949 | "version": "1.0.2", 1950 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 1951 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" 1952 | }, 1953 | "once": { 1954 | "version": "1.4.0", 1955 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1956 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1957 | "optional": true, 1958 | "requires": { 1959 | "wrappy": "1" 1960 | } 1961 | }, 1962 | "onetime": { 1963 | "version": "5.1.2", 1964 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 1965 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 1966 | "optional": true, 1967 | "requires": { 1968 | "mimic-fn": "^2.1.0" 1969 | } 1970 | }, 1971 | "p-limit": { 1972 | "version": "3.0.2", 1973 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", 1974 | "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", 1975 | "optional": true, 1976 | "requires": { 1977 | "p-try": "^2.0.0" 1978 | } 1979 | }, 1980 | "p-try": { 1981 | "version": "2.2.0", 1982 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1983 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1984 | "optional": true 1985 | }, 1986 | "parseurl": { 1987 | "version": "1.3.3", 1988 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1989 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1990 | }, 1991 | "path-to-regexp": { 1992 | "version": "0.1.7", 1993 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1994 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1995 | }, 1996 | "process-nextick-args": { 1997 | "version": "2.0.1", 1998 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1999 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 2000 | "optional": true 2001 | }, 2002 | "promise-polyfill": { 2003 | "version": "8.1.3", 2004 | "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", 2005 | "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" 2006 | }, 2007 | "protobufjs": { 2008 | "version": "6.10.1", 2009 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", 2010 | "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", 2011 | "requires": { 2012 | "@protobufjs/aspromise": "^1.1.2", 2013 | "@protobufjs/base64": "^1.1.2", 2014 | "@protobufjs/codegen": "^2.0.4", 2015 | "@protobufjs/eventemitter": "^1.1.0", 2016 | "@protobufjs/fetch": "^1.1.0", 2017 | "@protobufjs/float": "^1.0.2", 2018 | "@protobufjs/inquire": "^1.1.0", 2019 | "@protobufjs/path": "^1.1.2", 2020 | "@protobufjs/pool": "^1.1.0", 2021 | "@protobufjs/utf8": "^1.1.0", 2022 | "@types/long": "^4.0.1", 2023 | "@types/node": "^13.7.0", 2024 | "long": "^4.0.0" 2025 | }, 2026 | "dependencies": { 2027 | "@types/node": { 2028 | "version": "13.13.21", 2029 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.21.tgz", 2030 | "integrity": "sha512-tlFWakSzBITITJSxHV4hg4KvrhR/7h3xbJdSFbYJBVzKubrASbnnIFuSgolUh7qKGo/ZeJPKUfbZ0WS6Jp14DQ==" 2031 | } 2032 | } 2033 | }, 2034 | "proxy-addr": { 2035 | "version": "2.0.6", 2036 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 2037 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 2038 | "requires": { 2039 | "forwarded": "~0.1.2", 2040 | "ipaddr.js": "1.9.1" 2041 | } 2042 | }, 2043 | "pump": { 2044 | "version": "3.0.0", 2045 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 2046 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 2047 | "optional": true, 2048 | "requires": { 2049 | "end-of-stream": "^1.1.0", 2050 | "once": "^1.3.1" 2051 | } 2052 | }, 2053 | "pumpify": { 2054 | "version": "2.0.1", 2055 | "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", 2056 | "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", 2057 | "optional": true, 2058 | "requires": { 2059 | "duplexify": "^4.1.1", 2060 | "inherits": "^2.0.3", 2061 | "pump": "^3.0.0" 2062 | } 2063 | }, 2064 | "range-parser": { 2065 | "version": "1.2.1", 2066 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 2067 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 2068 | }, 2069 | "raw-body": { 2070 | "version": "2.4.0", 2071 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 2072 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 2073 | "requires": { 2074 | "bytes": "3.1.0", 2075 | "http-errors": "1.7.2", 2076 | "iconv-lite": "0.4.24", 2077 | "unpipe": "1.0.0" 2078 | } 2079 | }, 2080 | "readable-stream": { 2081 | "version": "3.6.0", 2082 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 2083 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 2084 | "optional": true, 2085 | "requires": { 2086 | "inherits": "^2.0.3", 2087 | "string_decoder": "^1.1.1", 2088 | "util-deprecate": "^1.0.1" 2089 | } 2090 | }, 2091 | "require-directory": { 2092 | "version": "2.1.1", 2093 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 2094 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 2095 | }, 2096 | "require-main-filename": { 2097 | "version": "2.0.0", 2098 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 2099 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" 2100 | }, 2101 | "retry-request": { 2102 | "version": "4.1.3", 2103 | "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", 2104 | "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", 2105 | "optional": true, 2106 | "requires": { 2107 | "debug": "^4.1.1" 2108 | }, 2109 | "dependencies": { 2110 | "debug": { 2111 | "version": "4.2.0", 2112 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 2113 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 2114 | "optional": true, 2115 | "requires": { 2116 | "ms": "2.1.2" 2117 | } 2118 | }, 2119 | "ms": { 2120 | "version": "2.1.2", 2121 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 2122 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 2123 | "optional": true 2124 | } 2125 | } 2126 | }, 2127 | "safe-buffer": { 2128 | "version": "5.2.1", 2129 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 2130 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 2131 | }, 2132 | "safer-buffer": { 2133 | "version": "2.1.2", 2134 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 2135 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 2136 | }, 2137 | "semver": { 2138 | "version": "6.3.0", 2139 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 2140 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 2141 | "optional": true 2142 | }, 2143 | "send": { 2144 | "version": "0.17.1", 2145 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 2146 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 2147 | "requires": { 2148 | "debug": "2.6.9", 2149 | "depd": "~1.1.2", 2150 | "destroy": "~1.0.4", 2151 | "encodeurl": "~1.0.2", 2152 | "escape-html": "~1.0.3", 2153 | "etag": "~1.8.1", 2154 | "fresh": "0.5.2", 2155 | "http-errors": "~1.7.2", 2156 | "mime": "1.6.0", 2157 | "ms": "2.1.1", 2158 | "on-finished": "~2.3.0", 2159 | "range-parser": "~1.2.1", 2160 | "statuses": "~1.5.0" 2161 | }, 2162 | "dependencies": { 2163 | "debug": { 2164 | "version": "2.6.9", 2165 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 2166 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 2167 | "requires": { 2168 | "ms": "2.0.0" 2169 | }, 2170 | "dependencies": { 2171 | "ms": { 2172 | "version": "2.0.0", 2173 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 2174 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 2175 | } 2176 | } 2177 | }, 2178 | "mime": { 2179 | "version": "1.6.0", 2180 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 2181 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 2182 | }, 2183 | "ms": { 2184 | "version": "2.1.1", 2185 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 2186 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 2187 | } 2188 | } 2189 | }, 2190 | "serve-static": { 2191 | "version": "1.14.1", 2192 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 2193 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 2194 | "requires": { 2195 | "encodeurl": "~1.0.2", 2196 | "escape-html": "~1.0.3", 2197 | "parseurl": "~1.3.3", 2198 | "send": "0.17.1" 2199 | } 2200 | }, 2201 | "set-blocking": { 2202 | "version": "2.0.0", 2203 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 2204 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 2205 | }, 2206 | "setprototypeof": { 2207 | "version": "1.1.1", 2208 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 2209 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 2210 | }, 2211 | "signal-exit": { 2212 | "version": "3.0.3", 2213 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 2214 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", 2215 | "optional": true 2216 | }, 2217 | "snakeize": { 2218 | "version": "0.1.0", 2219 | "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", 2220 | "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", 2221 | "optional": true 2222 | }, 2223 | "statuses": { 2224 | "version": "1.5.0", 2225 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 2226 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 2227 | }, 2228 | "stream-events": { 2229 | "version": "1.0.5", 2230 | "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", 2231 | "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", 2232 | "optional": true, 2233 | "requires": { 2234 | "stubs": "^3.0.0" 2235 | } 2236 | }, 2237 | "stream-shift": { 2238 | "version": "1.0.1", 2239 | "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", 2240 | "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", 2241 | "optional": true 2242 | }, 2243 | "streamsearch": { 2244 | "version": "0.1.2", 2245 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 2246 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" 2247 | }, 2248 | "string-width": { 2249 | "version": "4.2.0", 2250 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", 2251 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", 2252 | "requires": { 2253 | "emoji-regex": "^8.0.0", 2254 | "is-fullwidth-code-point": "^3.0.0", 2255 | "strip-ansi": "^6.0.0" 2256 | } 2257 | }, 2258 | "string_decoder": { 2259 | "version": "1.1.1", 2260 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 2261 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 2262 | "optional": true, 2263 | "requires": { 2264 | "safe-buffer": "~5.1.0" 2265 | }, 2266 | "dependencies": { 2267 | "safe-buffer": { 2268 | "version": "5.1.2", 2269 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 2270 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 2271 | "optional": true 2272 | } 2273 | } 2274 | }, 2275 | "strip-ansi": { 2276 | "version": "6.0.0", 2277 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 2278 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 2279 | "requires": { 2280 | "ansi-regex": "^5.0.0" 2281 | } 2282 | }, 2283 | "stubs": { 2284 | "version": "3.0.0", 2285 | "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", 2286 | "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", 2287 | "optional": true 2288 | }, 2289 | "teeny-request": { 2290 | "version": "7.0.1", 2291 | "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", 2292 | "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", 2293 | "optional": true, 2294 | "requires": { 2295 | "http-proxy-agent": "^4.0.0", 2296 | "https-proxy-agent": "^5.0.0", 2297 | "node-fetch": "^2.6.1", 2298 | "stream-events": "^1.0.5", 2299 | "uuid": "^8.0.0" 2300 | }, 2301 | "dependencies": { 2302 | "agent-base": { 2303 | "version": "6.0.1", 2304 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", 2305 | "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", 2306 | "optional": true, 2307 | "requires": { 2308 | "debug": "4" 2309 | } 2310 | }, 2311 | "debug": { 2312 | "version": "4.2.0", 2313 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 2314 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 2315 | "optional": true, 2316 | "requires": { 2317 | "ms": "2.1.2" 2318 | } 2319 | }, 2320 | "https-proxy-agent": { 2321 | "version": "5.0.0", 2322 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 2323 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 2324 | "optional": true, 2325 | "requires": { 2326 | "agent-base": "6", 2327 | "debug": "4" 2328 | } 2329 | }, 2330 | "ms": { 2331 | "version": "2.1.2", 2332 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 2333 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 2334 | "optional": true 2335 | }, 2336 | "uuid": { 2337 | "version": "8.3.1", 2338 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", 2339 | "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", 2340 | "optional": true 2341 | } 2342 | } 2343 | }, 2344 | "toidentifier": { 2345 | "version": "1.0.0", 2346 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 2347 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 2348 | }, 2349 | "tslib": { 2350 | "version": "1.13.0", 2351 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", 2352 | "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" 2353 | }, 2354 | "type-is": { 2355 | "version": "1.6.18", 2356 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 2357 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 2358 | "requires": { 2359 | "media-typer": "0.3.0", 2360 | "mime-types": "~2.1.24" 2361 | } 2362 | }, 2363 | "typedarray": { 2364 | "version": "0.0.6", 2365 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 2366 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 2367 | "optional": true 2368 | }, 2369 | "typedarray-to-buffer": { 2370 | "version": "3.1.5", 2371 | "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", 2372 | "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", 2373 | "optional": true, 2374 | "requires": { 2375 | "is-typedarray": "^1.0.0" 2376 | } 2377 | }, 2378 | "unique-string": { 2379 | "version": "2.0.0", 2380 | "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", 2381 | "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", 2382 | "optional": true, 2383 | "requires": { 2384 | "crypto-random-string": "^2.0.0" 2385 | } 2386 | }, 2387 | "universalify": { 2388 | "version": "1.0.0", 2389 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", 2390 | "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" 2391 | }, 2392 | "unpipe": { 2393 | "version": "1.0.0", 2394 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2395 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 2396 | }, 2397 | "util-deprecate": { 2398 | "version": "1.0.2", 2399 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2400 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 2401 | "optional": true 2402 | }, 2403 | "utils-merge": { 2404 | "version": "1.0.1", 2405 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2406 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 2407 | }, 2408 | "vary": { 2409 | "version": "1.1.2", 2410 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2411 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 2412 | }, 2413 | "websocket-driver": { 2414 | "version": "0.7.4", 2415 | "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", 2416 | "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", 2417 | "requires": { 2418 | "http-parser-js": ">=0.5.1", 2419 | "safe-buffer": ">=5.1.0", 2420 | "websocket-extensions": ">=0.1.1" 2421 | } 2422 | }, 2423 | "websocket-extensions": { 2424 | "version": "0.1.4", 2425 | "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", 2426 | "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" 2427 | }, 2428 | "whatwg-fetch": { 2429 | "version": "2.0.4", 2430 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", 2431 | "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" 2432 | }, 2433 | "which-module": { 2434 | "version": "2.0.0", 2435 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 2436 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 2437 | }, 2438 | "wrap-ansi": { 2439 | "version": "7.0.0", 2440 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 2441 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 2442 | "requires": { 2443 | "ansi-styles": "^4.0.0", 2444 | "string-width": "^4.1.0", 2445 | "strip-ansi": "^6.0.0" 2446 | } 2447 | }, 2448 | "wrappy": { 2449 | "version": "1.0.2", 2450 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2451 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 2452 | "optional": true 2453 | }, 2454 | "write-file-atomic": { 2455 | "version": "3.0.3", 2456 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", 2457 | "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", 2458 | "optional": true, 2459 | "requires": { 2460 | "imurmurhash": "^0.1.4", 2461 | "is-typedarray": "^1.0.0", 2462 | "signal-exit": "^3.0.2", 2463 | "typedarray-to-buffer": "^3.1.5" 2464 | } 2465 | }, 2466 | "xdg-basedir": { 2467 | "version": "4.0.0", 2468 | "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", 2469 | "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", 2470 | "optional": true 2471 | }, 2472 | "xmlhttprequest": { 2473 | "version": "1.8.0", 2474 | "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", 2475 | "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" 2476 | }, 2477 | "y18n": { 2478 | "version": "5.0.2", 2479 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.2.tgz", 2480 | "integrity": "sha512-CkwaeZw6dQgqgPGeTWKMXCRmMcBgETFlTml1+ZOO+q7kGst8NREJ+eWwFNPVUQ4QGdAaklbqCZHH6Zuep1RjiA==" 2481 | }, 2482 | "yallist": { 2483 | "version": "4.0.0", 2484 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 2485 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 2486 | "optional": true 2487 | }, 2488 | "yargs": { 2489 | "version": "16.0.3", 2490 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.0.3.tgz", 2491 | "integrity": "sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==", 2492 | "requires": { 2493 | "cliui": "^7.0.0", 2494 | "escalade": "^3.0.2", 2495 | "get-caller-file": "^2.0.5", 2496 | "require-directory": "^2.1.1", 2497 | "string-width": "^4.2.0", 2498 | "y18n": "^5.0.1", 2499 | "yargs-parser": "^20.0.0" 2500 | }, 2501 | "dependencies": { 2502 | "yargs-parser": { 2503 | "version": "20.2.1", 2504 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.1.tgz", 2505 | "integrity": "sha512-yYsjuSkjbLMBp16eaOt7/siKTjNVjMm3SoJnIg3sEh/JsvqVVDyjRKmaJV4cl+lNIgq6QEco2i3gDebJl7/vLA==" 2506 | } 2507 | } 2508 | }, 2509 | "yargs-parser": { 2510 | "version": "18.1.3", 2511 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", 2512 | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", 2513 | "requires": { 2514 | "camelcase": "^5.0.0", 2515 | "decamelize": "^1.2.0" 2516 | }, 2517 | "dependencies": { 2518 | "camelcase": { 2519 | "version": "5.3.1", 2520 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 2521 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" 2522 | } 2523 | } 2524 | } 2525 | } 2526 | } 2527 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "deploy": "firebase deploy --only functions,hosting", 6 | "serve": "firebase serve --only functions,hosting" 7 | }, 8 | "dependencies": { 9 | "firebase-admin": "^9.00.0", 10 | "firebase-functions": "^3.0.0", 11 | "hnpwa-api": "^0.2.3" 12 | }, 13 | "private": true 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hnpwa-api", 3 | "version": "0.2.4", 4 | "description": "Deploy a Hacker News API on your own domain.", 5 | "main": "index.js", 6 | "bin": { 7 | "hnpwa-api": "./cli.js" 8 | }, 9 | "scripts": { 10 | "tsc": "tsc", 11 | "build": "rm -rf dist && npm run tsc && cp ./package.json ./dist/package.json && cp .npmignore ./dist/.npmignore && cp README.md ./dist/README.md", 12 | "watch": "tsc -w", 13 | "serve": "npm run build && node ./dist/test/serve.test.js", 14 | "pack": "npm run build && npm pack dist", 15 | "pub:server": "npm run build && node ./dist/test/publish.test.js", 16 | "offline:test": "npm run build && node ./dist/test/offline.test.js" 17 | }, 18 | "dependencies": { 19 | "@types/compression": "^1.7.0", 20 | "@types/cors": "^2.8.7", 21 | "@types/cron": "^1.7.2", 22 | "@types/express": "^4.7.18", 23 | "@types/fs-extra": "^9.0.1", 24 | "@types/yargs": "^15.0.7", 25 | "compression": "^1.7.4", 26 | "cors": "^2.8.3", 27 | "cron": "^1.3.0", 28 | "express": "^4.15.3", 29 | "firebase": "^7.0.0", 30 | "firebase-admin": "^9.00.0", 31 | "firebase-functions": "^3.0.0", 32 | "fs-extra": "^9.0.0", 33 | "moment": "^2.22.2", 34 | "yargs": "^16.0.3" 35 | }, 36 | "devDependencies": { 37 | "nodemon": "^2.0.4", 38 | "typescript": "^3.9.6" 39 | }, 40 | "typings": "index.d.ts" 41 | } 42 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interfaces'; 2 | import { Story, itemMap, Item, User } from './interfaces'; 3 | import { stories } from './stories'; 4 | import { getItemAndComments } from './item'; 5 | import { getUser } from './user'; 6 | 7 | export type ApiFn = (options: {}) => Promise; 8 | export type ApiString = 'topstories' | 'newstories' | 'askstories' | 'showstories' | 'jobstories' | 'item' | 'user'; 9 | export interface ApiOptions { 10 | page: number; 11 | } 12 | export const MAX_PAGES: { [key: string]: number } = { 13 | "news": 10, 14 | "jobs": 1, 15 | "ask": 2, 16 | "show": 2, 17 | "newest": 10, 18 | "/": 10 19 | }; 20 | 21 | // Constant Hash of API topics 22 | export const apiMap: { [key: string]: ApiString } = { 23 | NEWS: 'topstories', 24 | NEWEST: 'newstories', 25 | ASK: 'askstories', 26 | SHOW: 'showstories', 27 | JOBS: 'jobstories', 28 | ITEM: 'item', 29 | USER: 'user' 30 | }; 31 | 32 | export interface Api { 33 | [key: string]: any; 34 | index(): { name: string }; 35 | news(options: ApiOptions): Promise; 36 | newest(options: ApiOptions): Promise; 37 | ask(options: ApiOptions): Promise; 38 | show(options: ApiOptions): Promise; 39 | jobs(options: ApiOptions): Promise; 40 | item(id: number): Promise; 41 | user(id: number): Promise; 42 | } 43 | 44 | export type ApiCreator = (app: firebase.app.App) => Api; 45 | 46 | /** 47 | * Helper method for generating a "story" feed. Top level keys like 48 | * "topstories" and "newstories" return an array of child keys which require 49 | * subsequent fetching. 50 | * @param key 51 | * @param options 52 | */ 53 | function storyFactory(key: ApiString, app: firebase.app.App) { 54 | return (options: ApiOptions) => stories(key, options, app); 55 | } 56 | 57 | function topicEndpointFactory(topic: string) { 58 | return { 59 | topic, 60 | url: `https://api.hnpwa.com/v0/${topic}/1.json`, 61 | maxPages: MAX_PAGES[topic] 62 | }; 63 | } 64 | 65 | function itemEndpointFactory() { 66 | return { 67 | topic: 'item', 68 | url: `https://api.hnpwa.com/v0/item/1.json`, 69 | maxPages: null 70 | }; 71 | } 72 | 73 | function userEndpointFactory() { 74 | return { 75 | topic: 'user', 76 | url: `https://api.hnpwa.com/v0/users/davideast.json`, 77 | maxPages: null 78 | }; 79 | } 80 | 81 | /** 82 | * The aggregated API for interfacing with Hacker News. 83 | */ 84 | const api: ApiCreator = (app: firebase.app.App) => { 85 | return { 86 | index(): any { 87 | return { 88 | name: 'Welcome to the HNPWA API', 89 | endpoints: [ 90 | topicEndpointFactory('news'), 91 | topicEndpointFactory('newest'), 92 | topicEndpointFactory('ask'), 93 | topicEndpointFactory('show'), 94 | topicEndpointFactory('jobs'), 95 | itemEndpointFactory(), 96 | userEndpointFactory() 97 | ] 98 | }; 99 | }, 100 | news(options: ApiOptions): Promise { 101 | return storyFactory(apiMap.NEWS, app)(options); 102 | }, 103 | newest(options: ApiOptions) { 104 | return storyFactory(apiMap.NEWEST, app)(options); 105 | }, 106 | ask(options: ApiOptions) { 107 | return storyFactory(apiMap.ASK, app)(options); 108 | }, 109 | show(options: ApiOptions) { 110 | return storyFactory(apiMap.SHOW, app)(options); 111 | }, 112 | jobs(options: ApiOptions) { 113 | return storyFactory(apiMap.JOBS, app)(options); 114 | }, 115 | user(id: number) { 116 | return getUser(id, app); 117 | }, 118 | async item(id: number) { 119 | const itemsWithComments = await getItemAndComments(id, app); 120 | if(itemsWithComments === null || itemsWithComments === undefined) { 121 | return null; 122 | } 123 | return itemMap(itemsWithComments); 124 | }, 125 | } 126 | }; 127 | 128 | export default api; 129 | -------------------------------------------------------------------------------- /src/api/interfaces.ts: -------------------------------------------------------------------------------- 1 | import * as url from 'url'; 2 | import * as moment from 'moment'; 3 | 4 | export interface HackerNewsItem { 5 | /** The item's unique id */ 6 | id: number; 7 | /** true if the item is deleted */ 8 | deleted?: boolean; 9 | /** The type of item. One of "job", "story", "comment", "poll", or "pollopt" */ 10 | type: 'job' | 'story' | 'comment' | 'poll' | 'pollopt'; 11 | /** The username of the item's author */ 12 | by: string; 13 | /** Creation date of the item, in Unix Time */ 14 | time: number; 15 | /** The comment, story or poll text. HTML */ 16 | text: string; 17 | /** true if the item is dead */ 18 | dead?: boolean; 19 | /** The comment's parent: either another comment or the relevant story */ 20 | parent: number; 21 | /** The pollopt's associated poll */ 22 | poll: number; 23 | /** The ids of the item's comments, in ranked display order */ 24 | kids: number[]; 25 | /** The URL of the story */ 26 | url?: string; 27 | /** The story's score, or the votes for a pollopt */ 28 | score: number; 29 | /** The title of the story, poll or job */ 30 | title: string; 31 | /** A list of related pollopts, in display order */ 32 | parts: number[]; 33 | /** In the case of stories or polls, the total comment count */ 34 | descendants: number; 35 | } 36 | 37 | /** 38 | * UI friendly "story" representation. Based on the HackerNewsItem which is 39 | * returned directly from the HN API. Used for feeds like "news", "jobs", 40 | * "ask", "show", etc... 41 | */ 42 | export interface Story { 43 | id: number; 44 | title: string; 45 | points?: number | null; 46 | user?: string | null; 47 | time: number; 48 | time_ago: string; 49 | comments_count: number; 50 | type: string; 51 | url?: string; 52 | domain?: string; 53 | } 54 | 55 | /** 56 | * UI friendly "item" representation. Based on the HackerNewsItem which is 57 | * returned directly from the HN API. Used mostly to represent comments. 58 | */ 59 | export interface Item { 60 | id: number; 61 | title: string; 62 | points: number | null; 63 | user: string | null; 64 | time: number; 65 | time_ago: string; 66 | content: string; 67 | deleted?: boolean; 68 | dead?: boolean; 69 | type: string; 70 | url?: string; 71 | domain?: string; 72 | comments: Item[]; 73 | level: number; 74 | comments_count: number; 75 | } 76 | 77 | export interface User { 78 | about?: string; 79 | created_time: number; 80 | created: string; 81 | id: string; 82 | karma: number; 83 | } 84 | 85 | /** 86 | * Represents a tree of an item and its comments. 87 | */ 88 | export interface HackerNewsItemTree { 89 | item: HackerNewsItem; 90 | comments: (HackerNewsItemTree | null)[]; 91 | } 92 | 93 | export const typeMapping: { [key: string]: string } = { story: 'link' }; 94 | 95 | /** 96 | * Map a JSON object from the HN API to a slimmer "story" model 97 | * @param item 98 | */ 99 | export const story = (item: HackerNewsItem): Story => { 100 | let commentsCount = item.descendants || 0; 101 | let story: Story = { 102 | id: item.id, 103 | title: item.title, 104 | points: item.score, 105 | user: item.by, 106 | time: item.time, 107 | time_ago: moment(item.time*1000).fromNow(), 108 | comments_count: commentsCount, 109 | type: typeMapping[item.type] || item.type 110 | }; 111 | 112 | story = parseUrl(item, story); 113 | 114 | // strip user name and points for jobs 115 | if (item.type == 'job') { 116 | story.user = story.points = null; 117 | } 118 | 119 | // Identify ask type 120 | if (item.type === 'story' && story.url!.match(/^item/i) && item.title.match(/^ask/i)) { 121 | story.type = 'ask'; 122 | } 123 | return story; 124 | }; 125 | 126 | /** 127 | * Remove trailing

and prepend

tags to comment content html. 128 | * https://github.com/cheeaun/node-hnapi/blob/master/lib/hnapi.js#L16 129 | * @param html 130 | */ 131 | function cleanText(html: string): string { 132 | if (!html) { return '' }; 133 | html = html.replace(/<\/p>/ig, ''); 134 | if (!html.match(/^

/i)){ html = '

' + html; } 135 | return html; 136 | } 137 | 138 | /** 139 | * Transform a story item's url and domain to a UI friendly format. 140 | * @param item 141 | * @param story 142 | */ 143 | function parseUrl(item: HackerNewsItem, story: any) { 144 | if (item.url) { 145 | story.url = item.url; 146 | story.domain = url.parse(item.url).hostname!.replace(/^www\./i, ''); 147 | } else { 148 | story.url = 'item?id=' + item.id; 149 | } 150 | return story; 151 | } 152 | 153 | /** 154 | * Transform the HackerNewsItemTree to a UI friendly Item model. 155 | * @param tree 156 | */ 157 | export function itemTransform(tree: HackerNewsItemTree, level = 0) { 158 | const { item } = tree; 159 | let mappedItem: Item = { 160 | id: item.id, 161 | title: item.title, 162 | points: item.score, 163 | user: item.by, 164 | time: item.time, 165 | time_ago: moment(tree.item.time*1000).fromNow(), 166 | type: typeMapping[item.type] || item.type, 167 | content: item.deleted ? '[deleted]' : cleanText(item.text), 168 | deleted: item.deleted, 169 | dead: item.dead, 170 | comments: [], 171 | comments_count: 0, 172 | level 173 | }; 174 | 175 | mappedItem = parseUrl(item, mappedItem); 176 | 177 | // strip user and points for jobs 178 | if (item.type == 'job') { 179 | mappedItem.user = null; 180 | mappedItem.points = null; 181 | } 182 | 183 | return mappedItem; 184 | } 185 | 186 | /** 187 | * Tramsforms a HackerNewsItemTree to a UI friendly Item model. This method 188 | * does more than you wish it would have to do. The HN API does not return 189 | * an accurate number of comments for a "story". Therefore Each tree must 190 | * be recursed and each item's comment array is reduced up to create the total. 191 | * @param tree 192 | */ 193 | export function itemMap(tree: HackerNewsItemTree) { 194 | const root = itemTransform(tree); 195 | // root level is a story, not a comment thread 196 | delete root.level; 197 | root.comments = recurseCommentTree(tree, 0); 198 | // gather comments count at root level 199 | root.comments_count = root.comments.reduce((acc, i) => { 200 | return acc += i.comments_count; 201 | }, 0) + root.comments.length; 202 | return root; 203 | } 204 | 205 | /** 206 | * Format and count comment items recursively. Each comment can contain 207 | * an array of comments. 208 | * @param tree 209 | * @param level 210 | */ 211 | function recurseCommentTree(tree: HackerNewsItemTree, level = 0) { 212 | let items: Item[] = [] 213 | tree.comments.forEach(comment => { 214 | if(comment === null) { return; } 215 | let mappedItem = itemTransform(comment, level); 216 | if(comment.comments) { 217 | mappedItem.comments = recurseCommentTree(comment, level + 1).filter(c => c !== null); 218 | mappedItem.comments_count = mappedItem.comments.length; 219 | } 220 | // gather comment counts at this level 221 | mappedItem.comments_count += mappedItem.comments 222 | .map(i => i.comments_count).reduce((acc, i) => acc += i, 0); 223 | items = [...items, mappedItem]; 224 | }); 225 | return items; 226 | } 227 | -------------------------------------------------------------------------------- /src/api/item.ts: -------------------------------------------------------------------------------- 1 | import { HackerNewsItem, HackerNewsItemTree } from '../api'; 2 | import * as firebase from 'firebase'; 3 | 4 | /** 5 | * Retrieves an "item" and its comment tree by an id. The building block of 6 | * the HN API is the "item". Everything is an item. A story, job, comments, and 7 | * nearly everything else. Each "item" has a list of "kids" which are the comments 8 | * for stories and the subcomments for comments. 9 | * 10 | * This method retrieves the individual item and then fetches each "kid" by its 11 | * id. The kids array is stripped out to save on bytes across the wire. 12 | * @param id 13 | * @param firebaseApp 14 | */ 15 | export async function getItemAndComments(id: number, firebaseApp: firebase.app.App): Promise { 16 | const itemRef = firebaseApp.database().ref('v0/item').child(id.toString()); 17 | const snap: firebase.database.DataSnapshot = await itemRef.once('value'); 18 | const item = snap.val() as HackerNewsItem; 19 | if(!item) { return null; } 20 | 21 | let comments: (HackerNewsItemTree | null)[] = []; 22 | if (item.kids && item.kids.length) { 23 | comments = await Promise.all(item.kids.map((kid: any) => getItemAndComments(kid, firebaseApp))); 24 | } 25 | 26 | // strip kids from response 27 | delete item.kids; 28 | 29 | // TODO(davideast): Poll parts 30 | delete item.parts; 31 | 32 | return { 33 | item, 34 | comments 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/api/stories.ts: -------------------------------------------------------------------------------- 1 | import { HackerNewsItem, Story, story } from '../api'; 2 | import * as firebase from 'firebase'; 3 | 4 | /** 5 | * Retrieve a set of "stories" based on the HN topic ("topstories", 6 | * "newstories", etc...). 7 | * @param topic - topstories, newstories, askstories, jobstories, etc... 8 | * @param options - { page: number } 9 | */ 10 | export async function stories(topic: string, options: {}, firebaseApp: firebase.app.App) { 11 | const opts = { page: 1, ...options }; 12 | const limit = 30; 13 | const startIndex = (opts.page-1) * limit; 14 | const endIndex = startIndex + limit; 15 | const ref = firebaseApp.database().ref('v0'); 16 | const storyRef = ref.child(topic).limitToFirst(limit * opts.page); 17 | const stories = await storyRef.once('value'); 18 | const items: number[] = stories.val().slice(startIndex, endIndex); 19 | const promises = items.map(id => ref.child('item').child(id.toString()).once('value')); 20 | const resolves: Story[] = await Promise.all(promises.map(async snap => { 21 | const snapshot = await snap; 22 | const item = snapshot.val() as HackerNewsItem; 23 | return story(item); 24 | })); 25 | return resolves; 26 | } 27 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import * as firebase from 'firebase'; 2 | import * as moment from 'moment'; 3 | import { User } from './'; 4 | 5 | /** 6 | * Get a user by their id and transform into a UI friendly JSON object. 7 | * @param id - The user's id 8 | */ 9 | export async function getUser(id: number, firebaseApp: firebase.app.App): Promise { 10 | const userRef = firebaseApp.database().ref('v0').child('user').child(id.toString()); 11 | const snapshot = await userRef.once('value'); 12 | const user = snapshot.val(); 13 | if (user && user.id) { 14 | return { 15 | about: user.about, 16 | created_time: user.created, 17 | created: moment(user.created * 1000).fromNow(), 18 | id: user.id, 19 | karma: user.karma 20 | }; 21 | } 22 | return null; 23 | } 24 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as yargs from 'yargs'; 4 | import * as express from 'express'; 5 | import api from './api'; 6 | import { createExpressApp, initializeApp } from './server'; 7 | import { buildFiles } from './offline/build'; 8 | 9 | interface FlagInputs { 10 | save: boolean; 11 | serve: boolean; 12 | port: number; 13 | offline: boolean; 14 | routerPath: string; 15 | v: boolean; 16 | version: boolean; 17 | } 18 | 19 | const argv: FlagInputs = yargs.argv as any; 20 | 21 | export interface AppOptions { port: number, offline: boolean, routerPath: string }; 22 | 23 | export const createApp = (opts: AppOptions): express.Express => { 24 | const { port, offline, routerPath } = opts; 25 | 26 | // TODO(davideast): Check for offline data if offline arg exists 27 | const expressApp = createExpressApp({ offline }); 28 | 29 | const router = express.Router(); 30 | router.use(routerPath, expressApp); 31 | 32 | const hostApp = express(); 33 | hostApp.use(router); 34 | return hostApp; 35 | } 36 | 37 | export const serve = (opts: AppOptions): express.Express => { 38 | const { port } = opts; 39 | const hostApp = createApp(opts); 40 | hostApp.listen(`${port}`, () => console.log(`Listening on ${port}!`)); 41 | return hostApp; 42 | } 43 | 44 | export const saveOfflineApi = async () => { 45 | const app = initializeApp({ firebaseAppName: `${Date.now()}` }); 46 | const hnapi = api(app); 47 | await buildFiles(hnapi); 48 | process.exit(0); 49 | }; 50 | 51 | // TODO(davideast): Use a CLI tool and maybe some chalk 52 | if(argv.serve) { 53 | serve({ 54 | port: argv.port || 3002, 55 | offline: argv.offline || false, 56 | routerPath: argv.routerPath || '' 57 | }); 58 | } else if(argv.save) { 59 | saveOfflineApi(); 60 | } else if(argv.v || argv.version) { 61 | const pkg = require('./package.json'); 62 | console.log(pkg.version); 63 | } else { 64 | const pkg = require('./package.json'); 65 | console.log(` 66 | 67 | hnpwa-api version ${pkg.version} 68 | "${pkg.description}" 69 | 70 | Available commands: 71 | --serve (--serve --port=4000 --offline=true --routerPath="/api") 72 | --save # Saves current HN data set to node_modules/hnpwa-api/offline 73 | -v # or --version 74 | 75 | `) 76 | } 77 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as functions from 'firebase-functions'; 2 | import * as cors from 'cors'; 3 | import * as express from 'express'; 4 | import { 5 | createExpressApp, 6 | ApiConfig, 7 | createBareExpressApp, 8 | FIREBASE_APP_NAME } from './server'; 9 | 10 | export { publisher } from './publish'; 11 | 12 | export type Trigger = functions.TriggerAnnotated & ((req: Express.Request, resp: Express.Response) => void); 13 | 14 | /** 15 | * Configure the Cloud Function Trigger based on options. The configuration 16 | * allows you to set cache expiration, enable cors, enable compression, 17 | * set the Firebase App instance name, and set a local port for testing. The 18 | * local port should not be used in production. 19 | * @param config 20 | */ 21 | export const trigger = (config?: ApiConfig): Trigger => { 22 | // merge defaults with config 23 | const mergedConfig : ApiConfig = { 24 | useCors: false, 25 | routerPath: '', 26 | cdnCacheExpiry: 600, 27 | browserCacheExpiry: 300, 28 | staleWhileRevalidate: 120, 29 | firebaseAppName: FIREBASE_APP_NAME, 30 | useCompression: true, 31 | offline: false, 32 | runWith: { 33 | memory: '256MB', 34 | timeoutSeconds: 300 35 | }, 36 | ...config, 37 | }; 38 | 39 | const expressApp = createExpressApp(mergedConfig); 40 | 41 | const router = express.Router(); 42 | router.use(mergedConfig.routerPath || '', expressApp); 43 | const tscRouterHack = router as any; 44 | 45 | // wrap in cors if cors enabled 46 | if (mergedConfig.useCors) { 47 | const corsServer = cors({ origin: true }); 48 | // Marked as any because of Type mismatch between CORS package and 49 | // Functions 50 | return functions 51 | .runWith(mergedConfig.runWith!) 52 | .https 53 | .onRequest((req: any, res: any) => { 54 | corsServer(req, res, () => { 55 | tscRouterHack(req, res); 56 | }); 57 | }); 58 | } 59 | else { 60 | return functions 61 | .runWith(mergedConfig.runWith!) 62 | .https.onRequest(tscRouterHack); 63 | } 64 | }; 65 | 66 | export const app = createBareExpressApp; 67 | 68 | export { HackerNewsItem, HackerNewsItemTree, Story } from './api/interfaces'; 69 | -------------------------------------------------------------------------------- /src/offline/api.ts: -------------------------------------------------------------------------------- 1 | import { ApiOptions, Story, Item, User } from '../api'; 2 | 3 | const TEST_USER: User = { about: '', created_time: 1400006274, created: "3 years ago", id: "davideast", karma: 22 }; 4 | 5 | const getFile = (topic: string): Story[] | Item[] => { 6 | return require(__dirname + `/${topic}.json`); 7 | }; 8 | 9 | const pageStories = (topic: string, options: ApiOptions): Story[] => { 10 | const stories = getFile(topic); 11 | const opts = { ...options, page: 1 }; 12 | const limit = 30; 13 | const startIndex = (opts.page - 1) * limit; 14 | const endIndex = startIndex + limit; 15 | return stories.slice(startIndex, endIndex); 16 | }; 17 | 18 | const offlineApi = (app: any) => { 19 | return { 20 | index() { return { name: 'Welcome to the HNPWA API' }; }, 21 | async news(options: ApiOptions) { 22 | return pageStories('news', options); 23 | }, 24 | async newest(options: ApiOptions) { 25 | return pageStories('newest', options); 26 | }, 27 | async ask(options: ApiOptions) { 28 | return pageStories('ask', options); 29 | }, 30 | async show(options: ApiOptions) { 31 | return pageStories('show', options); 32 | }, 33 | async jobs(options: ApiOptions) { 34 | return pageStories('jobs', options); 35 | }, 36 | async user(id: number) { 37 | return TEST_USER as User; 38 | }, 39 | async item(id: number): Promise { 40 | const items = await getFile('items'); 41 | return items.find(item => item.id == id)!; 42 | } 43 | }; 44 | }; 45 | 46 | export default offlineApi; 47 | -------------------------------------------------------------------------------- /src/offline/build.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import { Api, ApiOptions, Story, MAX_PAGES } from '../api'; 3 | 4 | export interface GetStoriesOptions { 5 | hnapi: Api; 6 | topic: string; 7 | opts: ApiOptions; 8 | max: number; 9 | acc?: Story[]; 10 | } 11 | 12 | /** 13 | * Retrieve all stories across all pages into a single array. A callback 14 | * is provided for each recursive call. 15 | */ 16 | export async function getStories( 17 | { hnapi, topic, opts, max, acc = [] }: GetStoriesOptions, 18 | onStories?: (stories: Story[], sum: Story[], page: number) => void): Promise { 19 | const news = await hnapi[topic](opts); 20 | const sum = acc.concat(news); 21 | const nextPage = opts.page + 1; 22 | if(onStories !== undefined) { 23 | onStories(news, sum, opts.page); 24 | } 25 | if(opts.page >= max) { 26 | return sum; 27 | } 28 | opts = { page: nextPage }; 29 | 30 | return getStories({ hnapi, topic, opts, max, acc: sum }, onStories); 31 | } 32 | 33 | /** 34 | * Create the offline files from the current online HN data set. 35 | * @param hnapi 36 | */ 37 | export async function buildFiles(hnapi: Api) { 38 | return new Promise((resolve, reject) => { 39 | try { 40 | let promiseHash: { [key: string]: Promise } = {}; 41 | Object.keys(MAX_PAGES).forEach(topic => { 42 | if(typeof hnapi[topic] !== 'function') { 43 | promiseHash[topic] = Promise.resolve([]); 44 | } else { 45 | const opts = { page: 1 }; 46 | const max = MAX_PAGES[topic]; 47 | promiseHash[topic] = getStories({ hnapi, topic, opts, max }, (stories, sum, page) => { 48 | const json = JSON.stringify(stories); 49 | fs.mkdirpSync(`${__dirname}/static/${topic}/`); 50 | fs.writeFileSync(`${__dirname}/static/${topic}/${page}.json`, json, 'utf8'); 51 | }); 52 | } 53 | }); 54 | 55 | Object.keys(promiseHash).forEach(async key => { 56 | const stories = await promiseHash[key]; 57 | if(stories.length > 0) { 58 | const json = JSON.stringify(stories); 59 | fs.writeFileSync(`${__dirname}/${key}.json`, json, 'utf8'); 60 | } 61 | 62 | if(key === 'news') { 63 | const itemPromises = stories.map(story => hnapi.item(story.id)); 64 | const allItems = await Promise.all(itemPromises); 65 | const itemsJson = JSON.stringify(allItems); 66 | fs.writeFile(`${__dirname}/items.json`, itemsJson, 'utf8', () => { 67 | resolve(); 68 | }); 69 | } 70 | }); 71 | } catch (e) { 72 | reject(e); 73 | } 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /src/publish/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as path from 'path'; 3 | import { CronJob } from 'cron'; 4 | import { getStories } from '../offline/build'; 5 | import { Api, Story, MAX_PAGES } from '../api'; 6 | import api from '../api'; 7 | import { initializeApp } from '../server'; 8 | 9 | export interface PublishOptions { 10 | interval: string | Date; 11 | dest: string; 12 | cwd: string; 13 | log?: Function; 14 | } 15 | 16 | /** 17 | * Write files to disk from the HNPWA API at a set interval. Fire the 18 | * afterWrite callback when all files are written. 19 | * @param options 20 | * @param afterWrite 21 | */ 22 | export function publisher(opts: PublishOptions, afterWrite: () => void) { 23 | let { interval, dest, cwd, log } = opts; 24 | // If the user does not provide a log function, use a noop fn 25 | if(typeof log !== 'function') { log = () => {} }; 26 | const job = new CronJob(interval, createPublishTask(dest, cwd, afterWrite, log)); 27 | return { 28 | _job: job, 29 | start: () => job.start(), 30 | stop: () => job.stop(), 31 | isRunning: () => job.running 32 | }; 33 | } 34 | 35 | /** 36 | * Create the API folder structure given the root path and the current 37 | * working directory. 38 | * 39 | * Ex: 40 | * /v0 41 | * / news 42 | * - 1.json 43 | * 44 | * @param root 45 | * @param cwd 46 | */ 47 | export function createFolderStructure(root: string, cwd: string, log: Function) { 48 | const rootPath = path.resolve(cwd, root); 49 | const itemPath = path.resolve(rootPath, 'item'); 50 | // Delete existing files first for a fresh deploy 51 | log('Deleting current data.'); 52 | fs.removeSync(rootPath); 53 | fs.mkdirpSync(rootPath); 54 | Object.keys(MAX_PAGES).forEach(topic => { 55 | // Dont need to create a root folder 56 | if(topic === '/') { return; } 57 | const maxPlusOne = MAX_PAGES[topic] + 1; 58 | const tenPagesLater = maxPlusOne + 10; 59 | let count = maxPlusOne; 60 | const topicPath = path.resolve(rootPath, topic); 61 | fs.mkdirpSync(topicPath); 62 | // Write a 404 json result for the next page outside of the 63 | // MAX_PAGES result. Currently use 10 pages of blank arrays 64 | // to be on the safe side. Outside of that extra 10 pages 65 | // a 404 should be dynamically generated. 66 | while(count < tenPagesLater) { 67 | const path404 = path.resolve(topicPath, `${count}.json`); 68 | fs.writeFileSync(path404, JSON.stringify([]), 'utf8'); 69 | log(`Wrote ${path404}.`); 70 | count++; 71 | } 72 | }); 73 | fs.mkdirpSync(itemPath); 74 | return rootPath; 75 | } 76 | 77 | 78 | /** 79 | * Creates the task of writing HNPWA API files to disk. 80 | * @param rootPath 81 | * @param afterWrite 82 | */ 83 | function createPublishTask(dest: string, cwd: string, afterWrite: Function, log: Function): () => void { 84 | return async () => { 85 | const rootPath = createFolderStructure(dest, cwd, log); 86 | const firebaseApp = initializeApp({ firebaseAppName: `${Date.now()}` }); 87 | const hnapi = api(firebaseApp); 88 | const promiseHash = writeTopics(hnapi, rootPath, log); 89 | const stories = await promiseHash.news; 90 | await writeItems(stories, hnapi, rootPath, log); 91 | afterWrite(); 92 | }; 93 | } 94 | 95 | /** 96 | * Downloads and writes the top-level HNPWA API topics to disk. 97 | * @param hnapi 98 | * @param rootPath 99 | */ 100 | function writeTopics(hnapi: Api, rootPath: string, log: Function) { 101 | let promiseHash: { [key: string]: Promise } = {}; 102 | Object.keys(MAX_PAGES).forEach(topic => { 103 | if (typeof hnapi[topic] !== 'function') { 104 | promiseHash[topic] = Promise.resolve([]); 105 | } 106 | else { 107 | const opts = { page: 1 }; 108 | const max = MAX_PAGES[topic]; 109 | promiseHash[topic] = getStories({ hnapi, topic, opts, max }, function getStories(stories, sum, page) { 110 | const topicPath = path.resolve(rootPath, topic, `${page.toString()}.json`); 111 | if (stories.length > 0) { 112 | fs.writeFileSync(topicPath, JSON.stringify(stories), 'utf8'); 113 | log(`Wrote ${topicPath}.`); 114 | } 115 | }); 116 | } 117 | }); 118 | return promiseHash; 119 | } 120 | 121 | /** 122 | * Writes a given set of items to individual disk files. 123 | * @param stories 124 | * @param hnapi 125 | * @param rootPath 126 | */ 127 | async function writeItems(stories: Story[], hnapi: Api, rootPath: string, log: Function) { 128 | try { 129 | const itemPromises = stories.map(story => hnapi.item(story.id)); 130 | const allItems = await Promise.all(itemPromises); 131 | return new Promise((resolve, reject) => { 132 | allItems.forEach(item => { 133 | if(item === null) { 134 | return; 135 | } 136 | const itemPath = path.resolve(rootPath, 'item', `${item.id.toString()}.json`); 137 | fs.writeFileSync(itemPath, JSON.stringify(item), 'utf8'); 138 | log(`Wrote ${itemPath}.`) 139 | }); 140 | resolve(); 141 | }); 142 | } catch(e) { 143 | log(e); 144 | throw e; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import * as firebase from 'firebase'; 2 | import 'firebase/database'; 3 | import * as express from 'express'; 4 | import { Express } from 'express'; 5 | import * as compression from 'compression'; 6 | import { Api } from './api'; 7 | import api from './api'; 8 | import offlineApi from './offline/api'; 9 | 10 | export const FIREBASE_APP_NAME = 'hnpwa-api'; 11 | 12 | export interface ApiConfig { 13 | useCors?: boolean; 14 | routerPath?: string; 15 | useCompression?: boolean; 16 | browserCacheExpiry?: number; 17 | cdnCacheExpiry?: number; 18 | staleWhileRevalidate?: number; 19 | firebaseAppName?: string; 20 | offline?: boolean; 21 | runWith?: { 22 | memory: '128MB' | '256MB' | '512MB' | '1GB' | '2GB'; 23 | timeoutSeconds: number; 24 | } 25 | } 26 | 27 | // Hash of route matchers 28 | export const routes = { 29 | NEWS_AND_STUFF: /^\/(news.json|newest.json|ask.json|show.json|jobs.json)$/, 30 | ITEM: /^\/item\/(\d+).json$/, 31 | USER: /^\/user\/(\w+).json$/, 32 | }; 33 | 34 | /** 35 | * Return a number within a maximum boundary. If the boundary of 10 is supplied 36 | * and 11 is passed as the page, 10 is returned. 37 | * 38 | * 10 = withinBounds(11, 10); 39 | * 1 = withinBounds(1, 10); 40 | * @param page 41 | * @param maxBounds 42 | */ 43 | function withinBounds(page: string, maxBounds = 10) { 44 | return Math.min(maxBounds, Math.max(1, parseInt(page, 10) || 1)); 45 | } 46 | 47 | export function getIndex(hnapi: Api) { 48 | return (req: any, res: any) => { 49 | res.jsonp(hnapi.index()); 50 | }; 51 | } 52 | 53 | /** 54 | * Creates an express route handler based on a Firebase App instance. 55 | * Get a list of "stories" based on the parameters provided. This API maps to the 56 | * traditional "top bar" (news, ask, jobs, show) in HN UI's. Paging is provided 57 | * through the ?page query param. 58 | * @param firebaseApp 59 | */ 60 | export function getNewsAndStuff(hnapi: Api) { 61 | return async (req: any, res: any) => { 62 | // "news" | "ask" | "jobs" | "show" etc... 63 | const topic = req.params[0].replace('.json', ''); 64 | const page = withinBounds(req.query.page); 65 | const newsies = await hnapi[topic]({ page }); 66 | res.jsonp(newsies); 67 | }; 68 | } 69 | 70 | /** 71 | * Creates an express route handler based on a Firebase App instance. 72 | * Get an item and it's comments from a request id param and return the JSON representation of 73 | * the user. 74 | * @param firebaseApp 75 | */ 76 | export function getItemAndComments(hnapi: Api) { 77 | return async (req: any, res: any) => { 78 | const itemId = req.params[0]; 79 | const item = await hnapi.item(itemId); 80 | res.jsonp(item); 81 | }; 82 | } 83 | 84 | /** 85 | * Creates an express route handler based on a Firebase App instance. 86 | * Get a user from a request id param and return the JSON representation of 87 | * the user. 88 | * @param firebaseApp 89 | */ 90 | export function getUserInfo(hnapi: Api) { 91 | return async (req: any, res: any) => { 92 | const userId = req.params[0]; 93 | const user = await hnapi.user(userId); 94 | res.jsonp(user); 95 | }; 96 | } 97 | 98 | /** 99 | * Create a data api depending on the offline configuration. If offline is disabled 100 | * the data api will retrieve data from Firebase server. Otherwise it will read from 101 | * local files. 102 | * @param config 103 | * @param firebaseApp 104 | */ 105 | function getApi(config: ApiConfig, firebaseApp: firebase.app.App) { 106 | let hnapi: Api; 107 | if(!config.offline) { 108 | hnapi = api(firebaseApp); 109 | } else { 110 | // firebase app does nothing here 111 | hnapi = offlineApi(firebaseApp); 112 | } 113 | return hnapi; 114 | } 115 | 116 | /** 117 | * Creates a firebase app instance based on the configuration name. 118 | * @param config 119 | */ 120 | export function initializeApp(config: ApiConfig): firebase.app.App { 121 | const possibleApp = firebase.apps.find(app => app!.name === config.firebaseAppName); 122 | let app = possibleApp!; 123 | if (!possibleApp) { 124 | app = firebase.initializeApp({ databaseURL: 'https://hacker-news.firebaseio.com/' }, config.firebaseAppName); 125 | } 126 | return app; 127 | } 128 | 129 | /** 130 | * Create a middleware handler for caching responses in the browser and CDN. 131 | * @param config 132 | */ 133 | function cacheControl(config: ApiConfig) { 134 | const { cdnCacheExpiry, browserCacheExpiry, staleWhileRevalidate } = config; 135 | return (req: any, res: any, next: Function) => { 136 | res.set('Cache-Control', `public, max-age=${browserCacheExpiry}, s-maxage=${cdnCacheExpiry}, stale-while-revalidate=${staleWhileRevalidate}`); 137 | next(); 138 | }; 139 | } 140 | 141 | 142 | function prettyPrint() { 143 | return (req: any, res: any, next: Function) => { 144 | const { print } = req.query; 145 | if (typeof print === 'undefined') { 146 | req.app.set('json spaces', 0); 147 | next(); 148 | return; 149 | } 150 | req.app.set('json spaces', 2); 151 | next(); 152 | }; 153 | } 154 | 155 | function prettyIndex() { 156 | return (req: any, res: any, next: Function) => { 157 | if (req.path === '/') { 158 | req.query.print = 'pretty' 159 | } 160 | next(); 161 | } 162 | } 163 | 164 | /** 165 | * Attaches express route handlers for the HNAPI given a Firebase App instance and 166 | * a user's config. 167 | * @param expressApp 168 | * @param config 169 | */ 170 | export function configureExpressRoutes(expressApp: Express, config: ApiConfig) { 171 | // Init firebase app instance 172 | const firebaseApp = initializeApp(config); 173 | // Create API instance from firebaseApp 174 | let hnapi = getApi(config, firebaseApp); 175 | 176 | expressApp.get('/', getIndex(hnapi)); 177 | expressApp.get(routes.NEWS_AND_STUFF, getNewsAndStuff(hnapi)); 178 | expressApp.get(routes.ITEM, getItemAndComments(hnapi)); 179 | expressApp.get(routes.USER, getUserInfo(hnapi)); 180 | expressApp.get('/favicon.ico', (req, res) => res.status(204).end()); 181 | expressApp.get('/_start', (req, res) => { 182 | res.set('Cache-Control', 'private'); 183 | res.send(true); 184 | }); 185 | 186 | return expressApp; 187 | } 188 | 189 | /** 190 | * Create an express application object based on the configuration passed in. 191 | * @param config 192 | */ 193 | export function createExpressApp(config: ApiConfig) { 194 | let expressApp: Express = express(); 195 | 196 | // Configure middleware 197 | if (config.useCompression) { expressApp.use(compression()); } 198 | expressApp.use(cacheControl(config)); 199 | expressApp.use(prettyIndex()); 200 | expressApp.use(prettyPrint()); 201 | 202 | if (config.offline) { expressApp.use(express.static(`${__dirname}/offline/static`)); } 203 | 204 | // apply routes 205 | expressApp = configureExpressRoutes(expressApp, config); 206 | 207 | return expressApp; 208 | } 209 | 210 | /** 211 | * Create an express app instance without the middleware configuration. 212 | * This is used for testing or applications not hosted on Firebase Hosting. 213 | * @param firebaseAppName 214 | */ 215 | export function createBareExpressApp(firebaseAppName = FIREBASE_APP_NAME) { 216 | return configureExpressRoutes(express(), { firebaseAppName }); 217 | } 218 | -------------------------------------------------------------------------------- /src/test/offline.test.ts: -------------------------------------------------------------------------------- 1 | import { buildFiles } from '../offline/build'; 2 | import api from '../api'; 3 | import { initializeApp, createExpressApp } from '../server'; 4 | 5 | const hnapi = api(initializeApp({})); 6 | 7 | buildFiles(hnapi).then(_ => { 8 | console.log('done building'); 9 | const app = createExpressApp({ 10 | offline: true, 11 | firebaseAppName: 'lol' 12 | }); 13 | 14 | app.listen(3002, () => console.log('Listening on 3002')); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /src/test/publish.test.ts: -------------------------------------------------------------------------------- 1 | import { publisher } from '../publish'; 2 | 3 | const runner = publisher({ 4 | interval: '* * * * *', 5 | dest: 'public/v0', 6 | cwd: process.cwd(), 7 | log: console.log 8 | }, () => { 9 | console.log('Done writing...') 10 | }); 11 | 12 | runner.start(); 13 | -------------------------------------------------------------------------------- /src/test/serve.test.ts: -------------------------------------------------------------------------------- 1 | import * as cli from '../cli'; 2 | 3 | cli.serve({ 4 | port: 3002, 5 | offline: false, 6 | routerPath: '/api/v0' 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "noImplicitAny": true, 6 | "sourceMap": false, 7 | "outDir": "dist", 8 | "strictNullChecks": true, 9 | "moduleResolution": "node", 10 | "declaration": true, 11 | "skipLibCheck": true 12 | }, 13 | "files": [ 14 | "src/index.ts", 15 | "src/test/serve.test.ts", 16 | "src/test/publish.test.ts", 17 | "src/test/offline.test.ts", 18 | "src/cli.ts" 19 | ] 20 | } --------------------------------------------------------------------------------