├── .clocignore
├── .gitignore
├── .idea
├── .gitignore
├── backupfire-firebase.iml
├── modules.xml
├── prettier.xml
└── vcs.xml
├── .prettierrc
├── .tool-versions
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE.md
├── Makefile
├── README.md
├── babel.config.js
├── examples
├── js
│ ├── .firebaserc
│ ├── .gitignore
│ └── functions
│ │ ├── .gitignore
│ │ ├── firebase.json
│ │ ├── index.js
│ │ └── package.json
└── ts
│ ├── .firebaserc
│ ├── .gitignore
│ ├── firebase.json
│ └── functions
│ ├── .gitignore
│ ├── package.json
│ ├── src
│ └── index.ts
│ └── tsconfig.json
├── extension
├── CHANGELOG.md
├── LICENSE
├── README.md
├── extension.yaml
└── functions
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ └── index.ts
│ └── tsconfig.json
├── jest.config.js
├── package-lock.json
├── package.json
├── src
├── _lib
│ ├── asyncMiddleware
│ │ └── index.ts
│ ├── exceptions
│ │ └── index.ts
│ ├── logging
│ │ └── index.ts
│ └── operation
│ │ └── index.ts
├── firestore
│ ├── _lib
│ │ └── client
│ │ │ └── index.ts
│ ├── backup
│ │ └── index.ts
│ ├── collections
│ │ └── index.ts
│ ├── index.ts
│ └── status
│ │ └── index.ts
├── index.ts
├── options.ts
├── storage
│ └── index.ts
├── types.ts
├── users
│ ├── index.ts
│ └── test.ts
└── version.ts
├── test
├── extension
│ ├── .firebaserc.example
│ ├── firebase.json
│ ├── index.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── yarn.lock
├── lib
│ ├── commonjs.js
│ └── ts.ts
└── server
│ ├── .env.example
│ ├── .firebaserc.example
│ ├── firebase.json
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ ├── scripts
│ └── seedUsers.js
│ └── tsconfig.json
├── tsconfig.json
└── types
└── firebase-tools.d.ts
/.clocignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | secrets
3 | lib
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node.js
2 | node_modules
3 |
4 | /lib
5 | /secrets
6 | .envrc
7 |
8 | # Extension
9 | /extension/functions/lib
10 | /extension/firebase-debug.log
11 |
12 | # Test projects
13 | /test/*/build
14 | /test/*/.firebaserc
15 | /test/*/.env
16 | /test/*/secrets
17 |
18 | # Firebase
19 | firebase-debug.log
20 |
21 | # VS Code
22 | /.vscode/*.log
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/backupfire-firebase.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/prettier.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 18.16.0
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "workbench.colorCustomizations": {
4 | "activityBar.activeBackground": "#f49300",
5 | "activityBar.activeBorder": "#bfffe6",
6 | "activityBar.background": "#f49300",
7 | "activityBar.foreground": "#15202b",
8 | "activityBar.inactiveForeground": "#15202b99",
9 | "activityBarBadge.background": "#bfffe6",
10 | "activityBarBadge.foreground": "#15202b",
11 | "statusBar.background": "#c17400",
12 | "statusBar.foreground": "#e7e7e7",
13 | "statusBarItem.hoverBackground": "#f49300",
14 | "titleBar.activeBackground": "#c17400",
15 | "titleBar.activeForeground": "#e7e7e7",
16 | "titleBar.inactiveBackground": "#c1740099",
17 | "titleBar.inactiveForeground": "#e7e7e799",
18 | "sash.hoverBorder": "#f49300",
19 | "statusBarItem.remoteBackground": "#c17400",
20 | "statusBarItem.remoteForeground": "#e7e7e7",
21 | "commandCenter.border": "#e7e7e799"
22 | },
23 | "peacock.remoteColor": "#c17400"
24 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning].
5 | This change log follows the format documented in [Keep a CHANGELOG].
6 |
7 | [semantic versioning]: https://semver.org
8 | [keep a changelog]: https://keepachangelog.com
9 |
10 | ## 1.9.1 - 2023-06-09
11 |
12 | ### Fixed
13 |
14 | - Fixed the agent incorrectly retrieving the env config from `functions.config()`.
15 |
16 | ## 1.9.0 - 2023-06-09
17 |
18 | ### Changed
19 |
20 | - Replaced `console.log` calls with `functions.logger` and added more debug logs around retrieving the env config.
21 |
22 | ## 1.8.1 - 2023-03-15
23 |
24 | ### Fixed
25 |
26 | - Excluded collection groups from exported collections when responding to the controller.
27 |
28 | ## 1.8.0 - 2023-03-15
29 |
30 | ### Added
31 |
32 | - Respond with collection groups back to the controller to display collection groups on the backup page.
33 |
34 | ## 1.7.0 - 2023-03-10
35 |
36 | ### Changed
37 |
38 | - Upgraded firebase-functions to the latest version (from 3 to 4).
39 |
40 | ### Fixed
41 |
42 | - Fixed the extension compatibility with the latest firebase-functions.
43 |
44 | ## 1.6.0 - 2023-03-10
45 |
46 | ### Changed
47 |
48 | - Upgraded firebase-tools.
49 |
50 | - Set the function `invoker` to `public` to enforce Firebase adding the permission to `allUsers`, to avoid [random permission bug](https://github.com/firebase/firebase-tools/issues/3965#issuecomment-1006005316).
51 |
52 | ### Fixed
53 |
54 | - Fix `memory` not setting to default memory runtime option (`1Gb`).
55 |
56 | ## 1.5.0 - 2023-02-28
57 |
58 | ### Added
59 |
60 | - Added logging to the agent endpoints to help with debugging and troubleshooting.
61 |
62 | ## 1.4.0 - 2022-06-03
63 |
64 | ### Changed
65 |
66 | - Set default `memory` (`1GB`) and `timeoutSeconds` (`540`) Firebase Functions runtime options. It solves the problem with the huge users' backups that either run out of memory or timeout.
67 |
68 | - Even further improved the memory usage by the users backup.
69 |
70 | - Updated dependencies to the latest supported versions.
71 |
72 | ### Added
73 |
74 | - Added delayed users backup feature. If the delay is requested, the agent will respond with a pending backup state. When the backup is completed, the agent will notify the controller. That prevents multiple backups caused by timeouts.
75 |
76 | ## 1.3.0 - 2022-06-01
77 |
78 | ### Fixed
79 |
80 | - Fixed compatibility with `firebase-functions` tripping over `timeoutSeconds` set to `undefined` and throwing `Field 'timeout', Invalid duration format, failed to parse seconds"`.
81 |
82 | ### Changed
83 |
84 | - Dramatically decrease the memory footprint of the user backup, making `memory` usage unnecessary.
85 |
86 | - Updated dependencies to the latest supported versions.
87 |
88 | ### Added
89 |
90 | - Added list files endpoint to the agent API that will help with Realtime Database backup integration and check for the backups' status (storage class, size, etc.)
91 |
92 | - Added create storage endpoint to simplify the integration process and automatically create the backups bucket with optimal defaults.
93 |
94 | - Added support for managing Backup Fire config with `.env` by setting `BACKUPFIRE_TOKEN` and `BACKUPFIRE_PASSWORD`.
95 |
96 | ## 1.2.0 - 2021-05-20
97 |
98 | ### Added
99 |
100 | - Added support for Firebase Extension runtime.
101 |
102 | - The agent now reports the GAE runtime version.
103 |
104 | ## 1.1.0 - 2021-04-01
105 |
106 | ### Changed
107 |
108 | - Updated dependencies.
109 |
110 | ## 1.0.3 - 2021-01-30
111 |
112 | ### Fixed
113 |
114 | - Finally fixed the `memory` option.
115 |
116 | ### Added
117 |
118 | - Added `timeout` option that allows to set the agent timeout in seconds (defaults to `60`; max `540`).
119 |
120 | ## 1.0.2 - 2021-01-22
121 |
122 | ### Fixed
123 |
124 | - Fixed the `memory` option in the agent that previously wasn't properly applied.
125 |
126 | ## 1.0.1 - 2021-01-14
127 |
128 | ### Fixed
129 |
130 | - Fixed an issue with `express-jwt` failing without specifying `algorithms`.
131 |
132 | ## 1.0.0 - 2021-01-13
133 |
134 | ### Changed
135 |
136 | - Upgraded dependencies to address the dependabot alerts.
137 |
138 | - Upgraded peer dependency `firebase-admin` to `>=9`.
139 |
140 | ## 0.19.0 - 2020-10-26
141 |
142 | ### Added
143 |
144 | - Added `memory` to the agent options to allow configuring the memory limit.
145 |
146 | ## 0.18.0 - 2020-07-17
147 |
148 | ### Changed
149 |
150 | - Accurately detect if the agent code is executed in a non-Functions environment (i.e., emulator or tests code) and suppress warnings.
151 |
152 | ## 0.17.0 - 2020-07-14
153 |
154 | ### Fixed
155 |
156 | - Prevent intercepting app events and exceptions if the app's also using Sentry.
157 |
158 | ### Changed
159 |
160 | - Improve the behavior of the agent crashed during initialization.
161 |
162 | ### Added
163 |
164 | - Send more information with the ping request: agent and Node.js versions, and the current region.
165 |
166 | ## 0.16.0 - 2020-06-20
167 |
168 | - Upgraded `@google-cloud/firestore` to the latest version (v3).
169 |
170 | ## 0.15.0 - 2020-06-18
171 |
172 | ### Changed
173 |
174 | - Upgraded `firebase-tools` to the latest version (v8).
175 |
176 | ## 0.14.0 - 2020-05-27
177 |
178 | ### Changed
179 |
180 | - Unless explicitly specified, make complete Firestore database backup by default.
181 |
182 | ### Added
183 |
184 | - Added the ability to choose between complete and selective Firestore backups.
185 |
186 | - Added the ability to specify collection groups when selective Firestore backup is chosen.
187 |
188 | ## 0.13.0 - 2020-04-12
189 |
190 | ### Added
191 |
192 | - Added the ability to specify the region via `region` option.
193 |
194 | ## 0.12.0 - 2019-12-22
195 |
196 | ### Changed
197 |
198 | - Ignore the emulator environment.
199 |
200 | - Improve exceptions tracking:
201 | - Stop automatic exception tracking to prevent accidental user data leaks.
202 | - Send additional information that could help with debugging (user ID, project ID, Node.js version).
203 | - General improvements.
204 |
205 | ## 0.11.0 - 2019-12-20
206 |
207 | ### Added
208 |
209 | - Added README and license.
210 |
211 | ## 0.10.0 - 2019-11-25
212 |
213 | ### Changed
214 |
215 | - **BREAKING**: Use `module.exports` to export the agent for CommonJS. If you import the agent using `require('@backupfire/firebase').default` you'll need to drop `.default`.
216 |
217 | ## 0.9.0 - 2019-11-15
218 |
219 | ### Added
220 |
221 | - Add Firestore collections list endpoint.
222 |
223 | - Enable selective Firestore backups:
224 | - Always specify collection ids during export, to enable selective restore.
225 | - Allow specifying ignored collections during Firestore backup.
226 |
227 | ## 0.8.0 - 2019-11-14
228 |
229 | ### Added
230 |
231 | - Capture exceptions to Sentry.
232 |
233 | ## 0.7.0 - 2019-11-13
234 |
235 | ### Changed
236 |
237 | - **BREAKING**: Move firebase-admin and firebase-functions to peer dependencies.
238 |
239 | ## 0.6.0 - 2019-10-30
240 |
241 | ### Added
242 |
243 | - Protected the storage update endpoint with admin password.
244 |
245 | ## 0.5.0 - 2019-10-19
246 |
247 | ### Fixed
248 |
249 | - Fixed more incompatibilities with the Node.js v10 runtime.
250 |
251 | ## 0.4.0 - 2019-10-18
252 |
253 | ### Fixed
254 |
255 | - Make the agent work with Node.js v10 runtime.
256 |
257 | ### Changed
258 |
259 | - Always deploy the agent to `us-central1` region.
260 |
261 | ## 0.3.0 - 2019-10-18
262 |
263 | ### Added
264 |
265 | - Added `debug` option which prints debug information to the log.
266 | - Print warnings when environment configuration isn't found or necessary variables are missing in the runtime environment.
267 |
268 | ### Changed
269 |
270 | - Ignore function instances with name not equal `backupfire`.
271 |
272 | ## 0.2.0 - 2019-10-17
273 |
274 | ### Changed
275 |
276 | - Replace the Backup Fire agent with a dummy HTTP handler and print a warning when `backupfire` key isn't found in the Functions environment configuration.
277 |
278 | ## 0.1.0 - 2019-10-15
279 |
280 | Initial version.
281 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, Backup Fire
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | - Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | - Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | - Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .DEFAULT_GOAL := build
2 | .PHONY: build test
3 |
4 | test:
5 | npx jest
6 |
7 | test-watch:
8 | npx jest --watch
9 |
10 | test-lib:
11 | node test/lib/commonjs.js
12 | npx tsx test/lib/ts.ts
13 |
14 | # Test projects
15 |
16 | deploy-test-server:
17 | @cd test/server && npx firebase deploy
18 |
19 | build-test-extension:
20 | @npx tsc test/extension/index.ts --esModuleInterop --outDir test/extension/build
21 |
22 | deploy-test-extension: build-test-extension
23 | @cd test/extension && npx firebase deploy
24 |
25 | # Staging & production
26 |
27 | build:
28 | @rm -rf lib
29 | @npx tsc
30 | @npx prettier "lib/**/*.[jt]s" --write --loglevel silent
31 | @cp package.json lib
32 | @cp *.md lib
33 | @rsync --archive --prune-empty-dirs --exclude '*.ts' --relative src/./ lib
34 |
35 | publish: build test-lib
36 | cd lib && npm publish --access public
37 |
38 | publish-next: build
39 | cd lib && npm publish --access public --tag next
40 |
41 | build-extension:
42 | @cd extension/functions && npm run build
43 |
44 | publish-extension: build-extension
45 | @cd extension && npx firebase ext:dev:publish backupfire/backupfire-agent
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Backup Fire agent for Firebase
2 |
3 | This is open-source core of [Backup Fire](https://backupfire.dev), a service that enables automatic backup of Firestore, the Firebase's DB, and Firebase authentication data.
4 |
5 | **To setup automatic backups, sign up at [Backup Fire](https://backupfire.dev), create a project, and follow the provided instructions. The examples below are provided only as a reference.**
6 |
7 | ## Installation
8 |
9 | The library is available as [an npm package](https://www.npmjs.com/package/@backupfire/firebase). To install it, run:
10 |
11 | ```bash
12 | npm install @backupfire/firebase --save
13 | # or with yarn
14 | yarn add @backupfire/firebase
15 | ```
16 |
17 | ## Usage
18 |
19 | **Make sure that you created and activated a project at [Backup Fire](https://backupfire.dev) first**.
20 |
21 | JavaScript:
22 |
23 | ```js
24 | // 1. Import the agent package
25 | const backupfireAgent = require('@backupfire/firebase')
26 |
27 | // 2. Create and export the agent
28 | exports.backupfire = backupfireAgent()
29 | ```
30 |
31 | TypeScript:
32 |
33 | ```ts
34 | // 1. Import the agent package
35 | import backupfireAgent from '@backupfire/firebase'
36 |
37 | // 2. Create and export the agent
38 | export const backupfire = backupfireAgent()
39 | ```
40 |
41 | Specify the region to deploy the agent function:
42 |
43 | ```ts
44 | import backupfireAgent from '@backupfire/firebase'
45 |
46 | export const backupfire = backupfireAgent({
47 | // See the list of available regions:
48 | // https://firebase.google.com/docs/functions/locations
49 | region: 'europe-west3'
50 | })
51 | ```
52 |
53 | ## License
54 |
55 | [BSD 3-Clause © Backup Fire](./LICENSE.md)
56 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', { targets: { node: 'current' } }],
4 | '@babel/preset-typescript',
5 | 'power-assert'
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/examples/js/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "backup-fire-playground"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/js/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | firebase-debug.log*
8 |
9 | # Firebase cache
10 | .firebase/
11 |
12 | # Firebase config
13 |
14 | # Uncomment this if you'd like others to create their own Firebase project.
15 | # For a team working on the same Firebase project(s), it is recommended to leave
16 | # it commented so all members can deploy to the same project(s) in .firebaserc.
17 | # .firebaserc
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 | *.pid.lock
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (http://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 |
49 | # Optional npm cache directory
50 | .npm
51 |
52 | # Optional eslint cache
53 | .eslintcache
54 |
55 | # Optional REPL history
56 | .node_repl_history
57 |
58 | # Output of 'npm pack'
59 | *.tgz
60 |
61 | # Yarn Integrity file
62 | .yarn-integrity
63 |
64 | # dotenv environment variables file
65 | .env
66 |
--------------------------------------------------------------------------------
/examples/js/functions/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/examples/js/functions/firebase.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/examples/js/functions/index.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions')
2 | const admin = require('firebase-admin')
3 | // 1. Import the agent package
4 | const backupfireAgent = require('@backupfire/firebase')
5 |
6 | admin.initializeApp()
7 |
8 | exports.helloWorld = functions.https.onRequest((_request, response) => {
9 | response.send('Hello from Firebase!')
10 | })
11 |
12 | // 2. Create and export the agent
13 | exports.backupfire = backupfireAgent()
14 |
--------------------------------------------------------------------------------
/examples/js/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "Cloud Functions for Firebase",
4 | "scripts": {
5 | "serve": "firebase serve --only functions",
6 | "shell": "firebase functions:shell",
7 | "start": "npm run shell",
8 | "deploy": "firebase deploy --only functions",
9 | "logs": "firebase functions:log"
10 | },
11 | "engines": {
12 | "node": "8"
13 | },
14 | "dependencies": {
15 | "@backupfire/firebase": "^0.5.0",
16 | "firebase-admin": "^8.6.0",
17 | "firebase-functions": "^3.3.0"
18 | },
19 | "devDependencies": {
20 | "firebase-functions-test": "^0.1.6"
21 | },
22 | "private": true
23 | }
24 |
--------------------------------------------------------------------------------
/examples/ts/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "backup-fire-playground"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/ts/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | firebase-debug.log*
8 |
9 | # Firebase cache
10 | .firebase/
11 |
12 | # Firebase config
13 |
14 | # Uncomment this if you'd like others to create their own Firebase project.
15 | # For a team working on the same Firebase project(s), it is recommended to leave
16 | # it commented so all members can deploy to the same project(s) in .firebaserc.
17 | # .firebaserc
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 | *.pid.lock
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (http://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 |
49 | # Optional npm cache directory
50 | .npm
51 |
52 | # Optional eslint cache
53 | .eslintcache
54 |
55 | # Optional REPL history
56 | .node_repl_history
57 |
58 | # Output of 'npm pack'
59 | *.tgz
60 |
61 | # Yarn Integrity file
62 | .yarn-integrity
63 |
64 | # dotenv environment variables file
65 | .env
66 |
--------------------------------------------------------------------------------
/examples/ts/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/ts/functions/.gitignore:
--------------------------------------------------------------------------------
1 | ## Compiled JavaScript files
2 | **/*.js
3 | **/*.js.map
4 |
5 | # Typescript v1 declaration files
6 | typings/
7 |
8 | node_modules/
--------------------------------------------------------------------------------
/examples/ts/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "build": "tsc",
5 | "serve": "npm run build && firebase serve --only functions",
6 | "shell": "npm run build && firebase functions:shell",
7 | "start": "npm run shell",
8 | "deploy": "firebase deploy --only functions",
9 | "logs": "firebase functions:log"
10 | },
11 | "engines": {
12 | "node": "8"
13 | },
14 | "main": "lib/index.js",
15 | "dependencies": {
16 | "@backupfire/firebase": "^0.5.0",
17 | "firebase-admin": "^8.6.0",
18 | "firebase-functions": "^3.3.0"
19 | },
20 | "devDependencies": {
21 | "typescript": "^3.2.2",
22 | "firebase-functions-test": "^0.1.6"
23 | },
24 | "private": true
25 | }
26 |
--------------------------------------------------------------------------------
/examples/ts/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as functions from 'firebase-functions'
2 | import * as admin from 'firebase-admin'
3 | // 1. Import the agent package.
4 | // Make sure that you have esModuleInterop set to true in compilerOptions in your tsconfig.json!
5 | import backupfireAgent from '@backupfire/firebase'
6 |
7 | admin.initializeApp()
8 |
9 | export const helloWorld = functions.https.onRequest((_request, response) => {
10 | response.send('Hello from Firebase!')
11 | })
12 |
13 | // 2. Create and export the agent
14 | export const backupfire = backupfireAgent()
15 |
--------------------------------------------------------------------------------
/examples/ts/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitReturns": true,
5 | "noUnusedLocals": true,
6 | "outDir": "lib",
7 | "sourceMap": true,
8 | "strict": true,
9 | "target": "es2017"
10 | },
11 | "compileOnSave": true,
12 | "include": [
13 | "src"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/extension/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning].
5 | This change log follows the format documented in [Keep a CHANGELOG].
6 |
7 | [semantic versioning]: https://semver.org
8 | [keep a changelog]: https://keepachangelog.com
9 |
10 | ## 1.8.1 - 2023-03-15
11 |
12 | ### Fixed
13 |
14 | - Excluded collection groups from exported collections when responding to the controller.
15 |
16 | ## 1.8.0 - 2023-03-15
17 |
18 | ### Added
19 |
20 | - Respond with collection groups back to the controller to display collection groups on the backup page.
21 |
22 | ## 1.7.0 - 2023-03-10
23 |
24 | ### Fixed
25 |
26 | - Fixed compatibility with the latest firebase-functions.
27 |
28 | ## 1.6.2 - 2023-03-10
29 |
30 | ### Fixed
31 |
32 | - Rollback the runtime version back to v16 as v18 is not yet supported by the dependencies.
33 |
34 | ## 1.6.1 - 2023-03-10
35 |
36 | ### Fixed
37 |
38 | - Removed unsupported `invoker`, which unfortunately voids the related update from `v1.6.0`.
39 |
40 | ## v1.6.0 - 2023-03-10
41 |
42 | ### Changed
43 |
44 | - Upgraded the Node.js runtime from v16 to v18.
45 |
46 | - Bumped the default RAM to 1Gb RAM and set the timeout to 9 minutes. It solves the problem with the huge users' backups that either run out of memory or timeout.
47 |
48 | - Set function invoker to public, to avoid [random permission bug](https://github.com/firebase/firebase-tools/issues/3965#issuecomment-1006005316).
49 |
50 | - Upgraded firebase-functions and firebase-admin.
51 |
52 | ## v1.5.0 - 2023-02-28
53 |
54 | ### Changed
55 |
56 | - The version now match the agent npm package version.
57 |
58 | ### Added
59 |
60 | - Added logging to the agent endpoints to help with debugging and troubleshooting.
61 |
62 | ## v1.1.0 - 2022-08-03
63 |
64 | ### Changed
65 |
66 | - Dramatically decrease the memory footprint of the user backup, making `memory` usage unnecessary.
67 |
68 | - Updated dependencies to the latest supported versions.
69 |
70 | ### Added
71 |
72 | - Added support for more Firestore location regions.
73 |
74 | - Added list files endpoint to the agent API that will help with Realtime Database backup integration and check for the backups' status (storage class, size, etc.)
75 |
76 | - Added create storage endpoint to simplify the integration process and automatically create the backups bucket with optimal defaults.
77 |
78 | ## v1.0.0 - 2021-09-27
79 |
80 | ### Fixed
81 |
82 | - Fixed missing `datastore.viewer` role that allows fetching the list of collections needed to perform selective backups.
83 |
84 | ## v0.2.0 - 2021-05-20
85 |
86 | Initial version.
87 |
--------------------------------------------------------------------------------
/extension/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 Backup Fire
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/extension/README.md:
--------------------------------------------------------------------------------
1 | # Backup Fire agent extension for Firebase
2 |
3 | This is open-source core of [Backup Fire](https://backupfire.dev) packed as a Firebase extension, a service that enables automatic backup of Firestore, the Firebase's DB, and Firebase authentication data.
4 |
5 | **To setup automatic backups, sign up at [Backup Fire](https://backupfire.dev), create a project, and follow the provided instructions. The examples below are provided only as a reference.**
6 |
7 | ## Installation
8 |
9 | To install the agent extension, please execute the command below in the project directory. You can also specify the project ID using the argument: `--project=PROJECT_ID`.
10 |
11 | ```bash
12 | firebase ext:install backupfire/backupfire-agent
13 | # or with project ID:
14 | firebase ext:install backupfire/backupfire-agent --project=PROJECT_ID
15 | ```
16 |
17 | During the installation you'll need to pick:
18 |
19 | - Region (pick the same region where your Firestore is deployed).
20 |
21 | - Agent token.
22 |
23 | - Admin password.
24 |
25 | To get the agent token you'll need to sign up and create a project at [Backup Fire](https://backupfire.dev).
26 |
27 | ## License
28 |
29 | [Apache-2.0 © Backup Fire](./LICENSE)
30 |
--------------------------------------------------------------------------------
/extension/extension.yaml:
--------------------------------------------------------------------------------
1 | name: backupfire-agent
2 | version: 1.8.1
3 | specVersion: v1beta
4 |
5 | displayName: Backup Fire Agent
6 | description: Backup your Firestore and Firebase Authentication data
7 |
8 | license: Apache-2.0
9 |
10 | sourceUrl: https://github.com/backupfire/backupfire-firebase/tree/master/extension
11 | releaseNotesUrl: https://github.com/backupfire/backupfire-firebase/blob/master/extension/CHANGELOG.md
12 |
13 | author:
14 | authorName: Sasha Koss
15 | url: https://github.com/kossnocorp
16 |
17 | billingRequired: true
18 |
19 | params:
20 | - param: LOCATION
21 | label: Where's your Firestore is deployed?
22 | description: >-
23 | Where's your Firestore is deployed? You can find it on the bottom of this
24 | page: https://console.firebase.google.com/project/_/firestore. Pick
25 | project and search for "Cloud Firestore location:" label.
26 | type: select
27 | options:
28 | - label: Iowa (us-central1) / United States (nam5)
29 | value: us-central1
30 | - label: Oregon (us-west1)
31 | value: us-west1
32 | - label: Los Angeles (us-west2)
33 | value: us-west2
34 | - label: Salt Lake City (us-west3)
35 | value: us-west3
36 | - label: Las Vegas (us-west4)
37 | value: us-west4
38 | - label: Montréal (northamerica-northeast1)
39 | value: northamerica-northeast1
40 | - label: South Carolina (us-east1)
41 | value: us-east1
42 | - label: Northern Virginia (us-east4)
43 | value: us-east4
44 | - label: São Paulo (southamerica-east1)
45 | value: southamerica-east1
46 | - label: Belgium (europe-west1) / Europe (eur3)
47 | value: europe-west1
48 | - label: London (europe-west2)
49 | value: europe-west2
50 | - label: Frankfurt (europe-west3)
51 | value: europe-west3
52 | - label: Warsaw (europe-central2)
53 | value: europe-central2
54 | - label: Zürich (europe-west6)
55 | value: europe-west6
56 | - label: Mumbai (asia-south1)
57 | value: asia-south1
58 | - label: Singapore (asia-southeast1)
59 | value: asia-southeast1
60 | - label: Jakarta (asia-southeast2)
61 | value: asia-southeast2
62 | - label: Hong Kong (asia-east2)
63 | value: asia-east2
64 | - label: Taiwan (asia-east1)
65 | value: asia-east1
66 | - label: Tokyo (asia-northeast1)
67 | value: asia-northeast1
68 | - label: Osaka (asia-northeast2)
69 | value: asia-northeast2
70 | - label: Seoul (asia-northeast3)
71 | value: asia-northeast3
72 | - label: Sydney (australia-southeast1)
73 | value: australia-southeast1
74 | default: us-central1
75 | required: true
76 | immutable: true
77 |
78 | - param: BACKUPFIRE_TOKEN
79 | label: Agent token
80 | description: >-
81 | The token is used to secure a connection between the controller
82 | (BackupFire) and the agent (this extension). Used to perform backups,
83 | retrieve meta information, etc.
84 | type: string
85 | required: true
86 |
87 | - param: BACKUPFIRE_PASSWORD
88 | label: Admin password
89 | description: >-
90 | The admin password is used as an extra layer of protection and ensures
91 | that only you can perform destructive operations such as retention policy
92 | change. The password is not stored on the Backup Fire side.
93 | type: string
94 | required: true
95 |
96 | roles:
97 | - role: datastore.importExportAdmin
98 | reason: Allows to export Firestore data
99 | - role: datastore.viewer
100 | reason: Allows to fetch the list of collections needed to perform selective backups
101 | - role: firebaseauth.admin
102 | reason: Allows to export Firebase Authentication data
103 | - role: storage.admin
104 | reason: Allows to adjust retention policy
105 |
106 | resources:
107 | - name: backupfire
108 | type: firebaseextensions.v1beta.function
109 | properties:
110 | location: ${LOCATION}
111 | runtime: nodejs16
112 | timeout: 540s
113 | availableMemoryMb: 1024
114 | httpsTrigger: {}
115 |
--------------------------------------------------------------------------------
/extension/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@backupfire/firebase-extension",
3 | "version": "1.8.1",
4 | "description": "Backup Fire Firebase extension",
5 | "homepage": "https://backupfire.dev",
6 | "repository": "https://github.com/backupfire/backupfire-firebase",
7 | "license": "Apache-2.0",
8 | "author": "Sasha Koss ",
9 | "main": "lib/index.js",
10 | "dependencies": {
11 | "@backupfire/firebase": "^1.8.1",
12 | "firebase-admin": "^11.5.0",
13 | "firebase-functions": "^4.2.1"
14 | },
15 | "devDependencies": {
16 | "typescript": "^4.9.5"
17 | },
18 | "scripts": {
19 | "build": "npm run clean && npm run compile",
20 | "clean": "rm -rf lib",
21 | "compile": "tsc"
22 | },
23 | "private": true
24 | }
25 |
--------------------------------------------------------------------------------
/extension/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import backupfire from '@backupfire/firebase'
2 | import * as admin from 'firebase-admin'
3 |
4 | admin.initializeApp()
5 |
6 | exports.backupfire = backupfire()
7 |
--------------------------------------------------------------------------------
/extension/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": "./src",
5 | "outDir": "./lib"
6 | },
7 | "include": ["./src"],
8 | "exclude": []
9 | }
10 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | roots: ['/src/']
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@backupfire/firebase",
3 | "version": "1.9.1",
4 | "description": "Backup Fire Firebase agent",
5 | "keywords": [
6 | "backup Firebase database",
7 | "backup Firestore",
8 | "backup Firebase Firestore",
9 | "backup Firebase authentication",
10 | "backup Firebase users",
11 | "backup Firebase data",
12 | "backup Google Firebase",
13 | "backup",
14 | "Backup Fire",
15 | "Firebase",
16 | "Firestore",
17 | "Firebase database",
18 | "Firebase authentication",
19 | "Firebase users"
20 | ],
21 | "main": "index.js",
22 | "homepage": "https://backupfire.dev",
23 | "repository": "https://github.com/backupfire/backupfire-firebase",
24 | "license": "BSD-3-Clause",
25 | "author": "Sasha Koss ",
26 | "devDependencies": {
27 | "@babel/core": "^7.13.14",
28 | "@babel/preset-env": "^7.13.12",
29 | "@babel/preset-typescript": "^7.13.0",
30 | "@types/body-parser": "^1.19.0",
31 | "@types/cors": "^2.8.10",
32 | "@types/express": "^4.17.13",
33 | "@types/express-jwt": "^6.0.4",
34 | "@types/jest": "^26.0.22",
35 | "@types/jsonwebtoken": "^8.5.8",
36 | "@types/mz": "^2.7.3",
37 | "@types/node": "^18.15.0",
38 | "@types/node-fetch": "^2.5.8",
39 | "@types/serve-static": "^1.13.10",
40 | "@types/sinon": "^9.0.11",
41 | "babel-loader": "^8.2.2",
42 | "babel-preset-power-assert": "^3.0.0",
43 | "esbuild": "^0.14.38",
44 | "firebase-admin": "^10.2.0",
45 | "firebase-functions": "^4.2.1",
46 | "jest": "^26",
47 | "power-assert": "^1.6.1",
48 | "prettier": "^2.2.1",
49 | "sinon": "^10.0.0",
50 | "tsx": "^3.12.3",
51 | "typescript": "^4.9.5"
52 | },
53 | "dependencies": {
54 | "@google-cloud/firestore": "^4.15.1",
55 | "@google-cloud/storage": "^6.0.1",
56 | "@sentry/node": "^6.19.7",
57 | "body-parser": "^1.20.0",
58 | "cors": "^2.8.5",
59 | "express": "^4.18.1",
60 | "express-jwt": "^7.7.5",
61 | "firebase-tools": "^11.24.1",
62 | "googleapis": "^100.0.0",
63 | "jsonwebtoken": "^8.5.1",
64 | "node-fetch": "^2.6.7"
65 | },
66 | "peerDependencies": {
67 | "firebase-admin": ">=9.6",
68 | "firebase-functions": ">=3.13.2"
69 | },
70 | "resolutions": {
71 | "@types/serve-static": "1.13.10"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/_lib/asyncMiddleware/index.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 |
3 | export default function asyncMiddleware(
4 | fn: express.RequestHandler
5 | ): express.RequestHandler {
6 | return (request, response, next) => {
7 | Promise.resolve(fn(request, response, next)).catch(next)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/_lib/exceptions/index.ts:
--------------------------------------------------------------------------------
1 | import { Hub, Integrations, NodeClient } from '@sentry/node'
2 | import { ErrorRequestHandler } from 'express'
3 | import * as functions from 'firebase-functions'
4 | import { BackupFireHTTPSHandler } from '../../types'
5 | import version from '../../version'
6 |
7 | let client: NodeClient
8 | let hub: Hub
9 |
10 | export function initExceptionsTracker() {
11 | client = new NodeClient({
12 | dsn: 'https://18820ae312bc46c4af3b672248d8a361@sentry.io/1819926',
13 | release: version,
14 | integrations: [new Integrations.FunctionToString()],
15 | defaultIntegrations: false,
16 | })
17 | hub = new Hub(client)
18 | }
19 |
20 | export const captureException: Hub['captureException'] = (...args) =>
21 | hub?.captureException(...args)
22 |
23 | export const configureExceptionsScope: Hub['configureScope'] = (...args) =>
24 | hub?.configureScope(...args)
25 |
26 | export const flushExceptions: NodeClient['flush'] = (...args) =>
27 | client?.flush(...args)
28 |
29 | export const exceptionHandlerMiddleware: ErrorRequestHandler = (
30 | err,
31 | request,
32 | _response,
33 | next
34 | ) => {
35 | configureExceptionsScope((scope) => {
36 | scope.setUser({ ip_address: request.ip })
37 | scope.setContext('request', request as any)
38 | })
39 | captureException(err)
40 | next(err)
41 | }
42 |
43 | export function createCrashedApp(err: any): BackupFireHTTPSHandler {
44 | return (_request: functions.Request, response: functions.Response) => {
45 | const eventId = captureException(err)
46 | response.status(500).send({
47 | message: `The Backup Fire agent has failed to initialiaze (see event ${eventId})`,
48 | })
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/_lib/logging/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The {@link maskString} function options.
3 | */
4 | export interface MaskStringOptions {
5 | /** If can display few chars */
6 | critical?: boolean
7 | }
8 |
9 | /**
10 | * The function masks passed input string or returns a string describing
11 | * the type of the input.
12 | *
13 | * @param input - the value to mask
14 | * @param options - the masking options
15 | * @returns the masked string or a string describing the type of the input
16 | */
17 | export function maskString(
18 | input: unknown,
19 | options?: MaskStringOptions
20 | ): string {
21 | if (typeof input !== 'string') {
22 | return `Type: ${typeof input}`
23 | }
24 |
25 | // Unless specified explicitly, mask all chars and display random length
26 | if (options?.critical !== false) {
27 | return '*'.repeat(8)
28 | }
29 |
30 | const length = input.length
31 | let unmaskedChars: number
32 |
33 | if (length <= 1) {
34 | unmaskedChars = 0
35 | } else if (length <= 4) {
36 | unmaskedChars = 1
37 | } else if (length <= 8) {
38 | unmaskedChars = 2
39 | } else {
40 | unmaskedChars = 3
41 | }
42 |
43 | const maskedPart = '*'.repeat(length - unmaskedChars)
44 | const unmaskedPart = input.slice(length - unmaskedChars)
45 |
46 | return `${maskedPart}${unmaskedPart}`
47 | }
48 |
--------------------------------------------------------------------------------
/src/_lib/operation/index.ts:
--------------------------------------------------------------------------------
1 | import { Response } from 'express'
2 | import { OperationStatus } from '../../firestore/status'
3 |
4 | export type FirestoreStatusResponse =
5 | | {
6 | state: 'completed'
7 | data: {
8 | usersCount: number | undefined
9 | size: string
10 | }
11 | }
12 | | {
13 | state: 'completed' | 'pending'
14 | data: { id: string; status: OperationStatus }
15 | }
16 |
17 | export type UsersStatusResponse =
18 | | {
19 | state: 'pending'
20 | }
21 | | {
22 | state: 'completed'
23 | data: {
24 | usersCount: number | undefined
25 | size: string
26 | }
27 | }
28 | | {
29 | state: 'failed'
30 | data: {
31 | reason: string
32 | }
33 | }
34 |
35 | export default function operationResponse(
36 | response: Response,
37 | payload: UsersStatusResponse | FirestoreStatusResponse
38 | ) {
39 | response.status(payload.state === 'failed' ? 400 : 200).send(payload)
40 | }
41 |
--------------------------------------------------------------------------------
/src/firestore/_lib/client/index.ts:
--------------------------------------------------------------------------------
1 | import firestore from '@google-cloud/firestore'
2 |
3 | const client = new firestore.v1.FirestoreAdminClient()
4 | export default client
5 |
--------------------------------------------------------------------------------
/src/firestore/backup/index.ts:
--------------------------------------------------------------------------------
1 | import { FirestoreBackupRequestBody } from '..'
2 | import firestore from '@google-cloud/firestore'
3 |
4 | export async function backupFirestore(
5 | projectId: string,
6 | collectionIds: string[] | undefined,
7 | options: FirestoreBackupRequestBody
8 | ) {
9 | const client = new firestore.v1.FirestoreAdminClient()
10 |
11 | const databaseName = client.databasePath(projectId, '(default)')
12 |
13 | // https://googleapis.dev/nodejs/firestore/latest/v1.FirestoreAdminClient.html#exportDocuments
14 | const [operation] = await client.exportDocuments({
15 | name: databaseName,
16 | outputUriPrefix: `gs://${options.storageId}/${options.path}`,
17 | collectionIds,
18 | })
19 |
20 | return operation.name
21 | }
22 |
--------------------------------------------------------------------------------
/src/firestore/collections/index.ts:
--------------------------------------------------------------------------------
1 | import * as admin from 'firebase-admin'
2 |
3 | export async function getCollections() {
4 | const collections = await admin.firestore().listCollections()
5 | return collections.map(collection => {
6 | return collection.path
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/src/firestore/index.ts:
--------------------------------------------------------------------------------
1 | import operationResponse from '../_lib/operation'
2 | import asyncMiddleware from '../_lib/asyncMiddleware'
3 | import { backupFirestore } from './backup'
4 | import { checkFirestoreBackupStatus } from './status'
5 | import { Response } from 'express'
6 | import { getCollections } from './collections'
7 | import * as functions from 'firebase-functions'
8 |
9 | export interface FirestoreBackupOptions {
10 | bucketsAllowlist?: string[]
11 | projectId: string
12 | }
13 |
14 | export type FirestoreBackupRequestBody =
15 | | FirestoreBackupRequestBodyComplete
16 | | FirestoreBackupRequestBodySelective
17 |
18 | export interface FirestoreBackupRequestBodyBase {
19 | storageId: string
20 | path: string
21 | }
22 |
23 | export interface FirestoreBackupRequestBodyComplete
24 | extends FirestoreBackupRequestBodyBase {
25 | mode: 'complete'
26 | }
27 |
28 | export interface FirestoreBackupRequestBodySelective
29 | extends FirestoreBackupRequestBodyBase {
30 | mode: 'selective'
31 | ignoreCollections?: string[]
32 | collectionGroups?: string[]
33 | }
34 |
35 | export function backupFirestoreMiddleware({
36 | bucketsAllowlist,
37 | projectId,
38 | }: FirestoreBackupOptions) {
39 | return asyncMiddleware(async (request, response) => {
40 | // TODO: Validate body
41 | const body = request.body as FirestoreBackupRequestBody
42 |
43 | if (body.mode === 'selective') {
44 | functions.logger.info('Requested Firestore backup', {
45 | // NOTE: Do not ...body here to avoid logging sensitive data
46 | mode: body.mode,
47 | ignoreCollections: body.ignoreCollections,
48 | collectionGroups: body.collectionGroups,
49 | bucketsAllowlist,
50 | projectId,
51 | })
52 |
53 | // Get all root-level collections
54 | const allCollections = await getCollections()
55 | const { ignoreCollections, collectionGroups } = body
56 | const exportedCollections = (
57 | ignoreCollections
58 | ? allCollections.filter((coll) => !ignoreCollections.includes(coll))
59 | : allCollections
60 | ).concat(collectionGroups || [])
61 |
62 | functions.logger.info('Initiating selective Firestore backup', {
63 | exportedCollections,
64 | ignoreCollections,
65 | })
66 |
67 | // Request selective Firestore backup
68 | const id = await backupFirestore(projectId, exportedCollections, body)
69 |
70 | if (id) {
71 | return respondWithStatus(response, id, {
72 | exportedCollections: exportedCollections.filter(
73 | (coll) => !collectionGroups?.includes(coll)
74 | ),
75 | ignoredCollections: ignoreCollections || [],
76 | exportedCollectionGroups: collectionGroups || [],
77 | })
78 | } else {
79 | return respondWithMissingId(response)
80 | }
81 | } else {
82 | functions.logger.info('Requested Firestore backup', {
83 | // NOTE: Do not ...body here to avoid logging sensitive data
84 | mode: body.mode,
85 | bucketsAllowlist,
86 | projectId,
87 | })
88 |
89 | // NOTE: Back-to-back logging here is to reflect the selective backup logging
90 | functions.logger.info('Initiating complete Firestore backup')
91 |
92 | // Request complete Firestore backup
93 | const id = await backupFirestore(projectId, undefined, body)
94 |
95 | if (id) {
96 | return respondWithStatus(response, id)
97 | } else {
98 | return respondWithMissingId(response)
99 | }
100 | }
101 | })
102 | }
103 |
104 | export interface FirestoreCheckBackupStatusRequestOptions {
105 | id: string
106 | }
107 |
108 | export function checkFirestoreBackupStatusMiddleware() {
109 | return asyncMiddleware(async (request, response) => {
110 | // TODO: Validate query
111 | const query =
112 | request.query as unknown as FirestoreCheckBackupStatusRequestOptions
113 |
114 | functions.logger.info('Requested Firestore backup status')
115 |
116 | return respondWithStatus(response, query.id)
117 | })
118 | }
119 |
120 | async function respondWithStatus(
121 | response: Response,
122 | id: string,
123 | extraData: object = {}
124 | ) {
125 | functions.logger.info('Checking the backup status', { id })
126 |
127 | const status = await checkFirestoreBackupStatus(id)
128 | const state = status.done ? 'completed' : 'pending'
129 |
130 | functions.logger.info('Responding with the backup status', { id, state })
131 |
132 | operationResponse(response, {
133 | state,
134 | data: Object.assign({ id, status }, extraData),
135 | })
136 | }
137 |
138 | function respondWithMissingId(response: Response) {
139 | functions.logger.info('Responding with missing id error')
140 |
141 | operationResponse(response, {
142 | state: 'failed',
143 | data: {
144 | reason:
145 | 'Firestore backup failed to initiate: the operation response has no id.',
146 | },
147 | })
148 | }
149 |
150 | export function getCollectionsMiddleware() {
151 | return asyncMiddleware(async (_request, response) => {
152 | functions.logger.info('Requested Firestore collections')
153 |
154 | const collections = await getCollections()
155 |
156 | functions.logger.info('Responding with Firestore collections', {
157 | collections,
158 | })
159 |
160 | response.send(collections)
161 | })
162 | }
163 |
--------------------------------------------------------------------------------
/src/firestore/status/index.ts:
--------------------------------------------------------------------------------
1 | import { google } from 'googleapis'
2 | import { GaxiosPromise } from 'gaxios'
3 |
4 | type OperationProgressWork = {
5 | estimatedWork: string // number as string
6 | completedWork: string // same as above
7 | }
8 |
9 | type OperationState =
10 | | 'STATE_UNSPECIFIED'
11 | | 'INITIALIZING'
12 | | 'PROCESSING'
13 | | 'CANCELLING'
14 | | 'FINALIZING'
15 | | 'SUCCESSFUL'
16 | | 'FAILED'
17 | | 'CANCELLED'
18 |
19 | type GetOperationResponse = GaxiosPromise<{
20 | name: string // 'projects/backup-fire-staging/databases/(default)/operations/ASA2MTAwMTYyNDEyChp0bHVhZmVkBxJsYXJ0bmVjc3Utc2Jvai1uaW1kYRQKLRI'
21 | metadata: {
22 | '@type': string // 'type.googleapis.com/google.firestore.admin.v1.ExportDocumentsMetadata'
23 | startTime: string // '2019-09-19T08:00:00.717192Z'
24 | endTime: string // '2019-09-19T08:00:48.955036Z'
25 | operationState: OperationState
26 | progressDocuments: OperationProgressWork
27 | progressBytes: OperationProgressWork
28 | collectionIds?: string[]
29 | outputUriPrefix: string // 'gs://backup-fire-staging.appspot.com/2019-09-19T08:00:00_29822'
30 | }
31 | done: boolean
32 | response: {
33 | '@type': 'type.googleapis.com/google.firestore.admin.v1.ExportDocumentsResponse'
34 | outputUriPrefix: string // 'gs://backup-fire-staging.appspot.com/2019-09-19T08:00:00_29822'
35 | }
36 | }>
37 |
38 | type OperationStatusProgress = {
39 | estimatedWork: number
40 | completedWork: number
41 | }
42 |
43 | export type OperationStatus = {
44 | startTime: string
45 | endTime?: string
46 | done?: boolean
47 | progressDocuments?: OperationStatusProgress
48 | progressBytes?: OperationStatusProgress
49 | }
50 |
51 | export async function checkFirestoreBackupStatus(
52 | id: string
53 | ): Promise {
54 | const auth = new google.auth.GoogleAuth({
55 | scopes: [
56 | 'https://www.googleapis.com/auth/cloud-platform',
57 | 'https://www.googleapis.com/auth/datastore'
58 | ]
59 | })
60 | const authClient = await auth.getClient()
61 | const firestore = google.firestore({
62 | version: 'v1',
63 | auth: authClient
64 | })
65 | const response = await (firestore.projects.databases.operations.get({
66 | name: id
67 | }) as GetOperationResponse)
68 |
69 | const {
70 | done,
71 | metadata: { startTime, endTime, progressDocuments, progressBytes }
72 | } = response.data
73 |
74 | return {
75 | startTime,
76 | endTime,
77 | done,
78 | progressDocuments: progressDocuments && parseProgress(progressDocuments),
79 | progressBytes: progressBytes && parseProgress(progressBytes)
80 | }
81 | }
82 |
83 | function parseProgress(
84 | progress: OperationProgressWork
85 | ): OperationStatusProgress {
86 | return {
87 | estimatedWork: parseInt(progress.estimatedWork),
88 | completedWork: parseInt(progress.completedWork)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import bodyParser from 'body-parser'
2 | import cors from 'cors'
3 | import express from 'express'
4 | import { expressjwt as jwt } from 'express-jwt'
5 | import * as functions from 'firebase-functions'
6 | import fetch from 'node-fetch'
7 | import { format } from 'url'
8 | import {
9 | backupFirestoreMiddleware,
10 | checkFirestoreBackupStatusMiddleware,
11 | getCollectionsMiddleware,
12 | } from './firestore'
13 | import {
14 | defaultControllerDomain,
15 | defaultMemory,
16 | defaultRegion,
17 | defaultTimeout,
18 | } from './options'
19 | import {
20 | createStorageMiddleware,
21 | listFilesMiddleware,
22 | storageListMiddleware,
23 | updateStorageMiddleware,
24 | } from './storage'
25 | import {
26 | AgentOptions,
27 | BackupFireEnvConfig,
28 | BackupFireHTTPSHandler,
29 | BackupFireOptions,
30 | RuntimeEnvironment,
31 | } from './types'
32 | import { backupUsersMiddleware } from './users'
33 | import version from './version'
34 | import {
35 | configureExceptionsScope,
36 | createCrashedApp,
37 | exceptionHandlerMiddleware,
38 | initExceptionsTracker,
39 | } from './_lib/exceptions'
40 | import { maskString } from './_lib/logging'
41 |
42 | export enum BackupFireConfig {
43 | Token = 'BACKUPFIRE_TOKEN',
44 | Password = 'BACKUPFIRE_PASSWORD',
45 | Domain = 'BACKUPFIRE_DOMAIN',
46 | Allowlist = 'BACKUPFIRE_ALLOWLIST',
47 | }
48 |
49 | // Fallback for CommonJS
50 | module.exports = backupFire
51 |
52 | /**
53 | * Creates Backup Fire Firebase Functions HTTPS handler.
54 | *
55 | * @param options - The Backup Fire agent options
56 | */
57 | export default function backupFire(agentOptions?: AgentOptions) {
58 | initExceptionsTracker()
59 |
60 | try {
61 | // Use dummy handler if it's emulator or not deployed to Functions
62 | if (isEmulator() || !isDeployedToFunctions())
63 | return dummyHandler({
64 | region: agentOptions?.region,
65 | memory: agentOptions?.memory,
66 | timeout: agentOptions?.timeout,
67 | })
68 |
69 | // Derive Backup Fire options from environment configuration
70 | const envConfig = getEnvConfig()
71 |
72 | // If options aren't set, use dummy handler instead
73 | if (!envConfig) {
74 | functions.logger.warn(
75 | `Warning: the Backup Fire configuration is missing, either set BACKUPFIRE_TOKEN and BACKUPFIRE_PASSWORD or set backupfire.token and backupfire.password as env config values. Running a dummy HTTP handler instead of the Backup Fire agent...`
76 | )
77 | return dummyHandler({ region: agentOptions?.region })
78 | }
79 |
80 | const options: BackupFireOptions = Object.assign(
81 | {
82 | controllerDomain: envConfig.domain,
83 | controllerToken: envConfig.token,
84 | adminPassword: envConfig.password,
85 | bucketsAllowlist: envConfig.allowlist?.split(','),
86 | debug: envConfig.debug === 'true',
87 | },
88 | agentOptions
89 | )
90 |
91 | // Get runtime environment (Firebase project ID, region, etc)
92 |
93 | const runtimeEnv = getRuntimeEnv(options)
94 |
95 | // If the function name isn't backupfire, use dummy handler
96 | if (runtimeEnv.functionName !== 'backupfire') {
97 | if (options.debug)
98 | functions.logger.debug(
99 | `The function name isn't "backupfire" (${runtimeEnv.functionName}). Running a dummy HTTP handler instead of the Backup Fire agent...`
100 | )
101 | return dummyHandler(options)
102 | }
103 |
104 | // If some of the variables are missing, use dummy handler
105 | if (!isCompleteRuntimeEnv(runtimeEnv)) {
106 | functions.logger.warn(
107 | 'Warning: runtime environment is incomplete:',
108 | prettyJSON(runtimeEnv)
109 | )
110 | functions.logger.warn(
111 | 'Running a dummy HTTP handler instead of the Backup Fire agent...'
112 | )
113 | return dummyHandler(options)
114 | }
115 |
116 | // Set additional context
117 | configureExceptionsScope((scope) => {
118 | scope.setUser({ id: envConfig.token })
119 | scope.setTag('project_id', runtimeEnv.projectId)
120 | scope.setTag('node_version', process.version)
121 | })
122 |
123 | if (options.debug) {
124 | functions.logger.debug(
125 | 'Initializing Backup Fire agent with options:',
126 | prettyJSON(options)
127 | )
128 | functions.logger.debug('Runtime environment:', prettyJSON(runtimeEnv))
129 | }
130 |
131 | // Send the initialization ping to the controller
132 | sendInitializationPing(options, runtimeEnv)
133 |
134 | return httpsHandler({
135 | handler: createApp(runtimeEnv, options),
136 | agentOptions,
137 | runtimeEnv,
138 | })
139 | } catch (err) {
140 | return httpsHandler({
141 | handler: createCrashedApp(err),
142 | agentOptions,
143 | })
144 | }
145 | }
146 |
147 | /**
148 | * Creates [Express] app that serves as an agent that provide API to
149 | * the Backup Fire controller.
150 | *
151 | * @param runtimeEnv - The runtime environment variables
152 | * @param options - The Backup Fire agent options
153 | *
154 | * [Express]: https://expressjs.com/
155 | */
156 | export function createApp(
157 | runtimeEnv: RuntimeEnvironment,
158 | options: BackupFireOptions
159 | ): BackupFireHTTPSHandler {
160 | // Create Express app that would be mounted as a function
161 | const app = express()
162 |
163 | // Protect Backup Fire API with token authorization
164 | app.use(jwt({ secret: options.controllerToken, algorithms: ['HS256'] }))
165 |
166 | // Parse JSON body
167 | app.use(bodyParser.json())
168 |
169 | // Allow requests from web
170 | app.use(cors({ origin: true }))
171 |
172 | const globalOptions = { bucketsAllowlist: options.bucketsAllowlist }
173 |
174 | // Backup Firestore
175 | app.post(
176 | '/firestore',
177 | backupFirestoreMiddleware({
178 | projectId: runtimeEnv.projectId,
179 | ...globalOptions,
180 | })
181 | )
182 | // Check Firestore backup status
183 | app.get('/firestore/status', checkFirestoreBackupStatusMiddleware())
184 |
185 | // List collections
186 | app.get('/firestore/collections', getCollectionsMiddleware())
187 |
188 | // Backup Firebase users
189 | app.post(
190 | '/users',
191 | backupUsersMiddleware({
192 | projectId: runtimeEnv.projectId,
193 | controllerToken: options.controllerToken,
194 | controllerDomain: options.controllerDomain,
195 | agentURL: agentURL(runtimeEnv),
196 | ...globalOptions,
197 | })
198 | )
199 |
200 | // List storage
201 | app.get('/storage', storageListMiddleware(globalOptions))
202 | // Create storage
203 | app.post('/storage', createStorageMiddleware(globalOptions))
204 | // Update storage
205 | app.put(
206 | '/storage/:storageId',
207 | updateStorageMiddleware({
208 | adminPassword: options.adminPassword,
209 | ...globalOptions,
210 | })
211 | )
212 | // List files in the storage
213 | app.get('/storage/:storageId/files', listFilesMiddleware(globalOptions))
214 |
215 | app.use(exceptionHandlerMiddleware)
216 |
217 | return app
218 | }
219 |
220 | interface HTTPSHandlerProps {
221 | handler: BackupFireHTTPSHandler
222 | agentOptions: AgentOptions | undefined
223 | runtimeEnv?: RuntimeEnvironment
224 | }
225 |
226 | function httpsHandler({
227 | handler,
228 | agentOptions,
229 | runtimeEnv,
230 | }: HTTPSHandlerProps) {
231 | if (runtimeEnv?.extensionId) {
232 | return functions.https.onRequest(handler)
233 | } else {
234 | return functions
235 | .runWith({
236 | ...getRuntimeOptions(agentOptions),
237 | secrets: Object.values(BackupFireConfig),
238 | // Sometimes Firebase Functions doesn't set the invoker correctly:
239 | // See: https://github.com/firebase/firebase-tools/issues/3965#issuecomment-1006005316
240 | invoker: 'public',
241 | })
242 | .region(agentOptions?.region || defaultRegion)
243 | .https.onRequest(handler)
244 | }
245 | }
246 |
247 | function sendInitializationPing(
248 | options: BackupFireOptions,
249 | runtimeEnv: RuntimeEnvironment
250 | ) {
251 | // TODO: Report failure if the request fails
252 | const pingURL = format({
253 | hostname: options.controllerDomain || defaultControllerDomain,
254 | protocol: 'https',
255 | pathname: '/ping',
256 | query: {
257 | agentVersion: version,
258 | nodeVersion: process.version,
259 | region: runtimeEnv.region,
260 | token: options.controllerToken,
261 | projectId: runtimeEnv.projectId,
262 | runtime: runtimeEnv.region,
263 | agentURL: agentURL(runtimeEnv),
264 | },
265 | })
266 | return fetch(pingURL)
267 | }
268 |
269 | function agentURL(runtimeEnv: RuntimeEnvironment) {
270 | const { region, projectId, functionName, extensionId } = runtimeEnv
271 | return `https://${region}-${projectId}.cloudfunctions.net/${
272 | extensionId ? `ext-${extensionId}-${functionName}` : functionName
273 | }`
274 | }
275 |
276 | function isEmulator() {
277 | return process.env.FUNCTIONS_EMULATOR === 'true'
278 | }
279 |
280 | function isDeployedToFunctions() {
281 | // Detect if the agent is deployed to Firebase Functions
282 | // See: https://cloud.google.com/functions/docs/env-var#environment_variables_set_automatically
283 | return !!process.env.FUNCTION_NAME || !!process.env.FUNCTION_TARGET
284 | }
285 |
286 | function getRuntimeEnv(
287 | options: BackupFireOptions
288 | ): Partial | RuntimeEnvironment {
289 | const extensionId = process.env.EXT_INSTANCE_ID
290 |
291 | return {
292 | region: extensionId
293 | ? process.env.LOCATION
294 | : options.region || defaultRegion,
295 | // Node.js v8 runtime sets GCP_PROJECT, while v10 uses depricated GCLOUD_PROJECT
296 | projectId: process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT,
297 | // Node.js v8 runtime uses FUNCTION_NAME, v10 — FUNCTION_TARGET
298 | // See: https://cloud.google.com/functions/docs/env-var#environment_variables_set_automatically
299 | functionName: process.env.FUNCTION_NAME || process.env.FUNCTION_TARGET,
300 | extensionId,
301 | }
302 | }
303 |
304 | function getEnvConfig(): BackupFireEnvConfig | undefined {
305 | functions.logger.debug('Retrieving the env config')
306 |
307 | const token = process.env[BackupFireConfig.Token]
308 | const password = process.env[BackupFireConfig.Password]
309 | const domain = process.env[BackupFireConfig.Domain]
310 | const allowlist = process.env[BackupFireConfig.Allowlist]
311 |
312 | const envConfig = functions.config().backupfire as
313 | | BackupFireEnvConfig
314 | | undefined
315 |
316 | functions.logger.debug('The env variables found', {
317 | token: maskString(token, { critical: false }),
318 | password: maskString(password),
319 | domain,
320 | allowlist,
321 | })
322 |
323 | if (envConfig) {
324 | functions.logger.debug('The Firebase env config found', {
325 | password: maskString(envConfig.password),
326 | token: maskString(envConfig.token, { critical: false }),
327 | })
328 | } else {
329 | functions.logger.debug('The Firebase env config is not found')
330 | }
331 |
332 | // First, check if the env contains the token & password
333 | if (token && password) {
334 | functions.logger.debug('Using env variable values')
335 | return { token, password, domain, allowlist }
336 | }
337 | // Otherwise, return the env config value
338 | else {
339 | functions.logger.debug('Using Firebase env config values')
340 | return envConfig
341 | }
342 | }
343 |
344 | function isCompleteRuntimeEnv(
345 | runtimeEnv: Partial | RuntimeEnvironment
346 | ): runtimeEnv is RuntimeEnvironment {
347 | return (
348 | !!runtimeEnv.functionName && !!runtimeEnv.projectId && !!runtimeEnv.region
349 | )
350 | }
351 |
352 | function dummyHandler(
353 | options: Pick
354 | ) {
355 | const runtimeOptions: functions.RuntimeOptions = {}
356 | if (options?.memory) runtimeOptions.memory = options.memory
357 | if (options?.timeout) runtimeOptions.timeoutSeconds = options.timeout
358 |
359 | return functions
360 | .runWith(getRuntimeOptions(options))
361 | .region(options.region || defaultRegion)
362 | .https.onRequest((_req, resp) => {
363 | resp.end()
364 | })
365 | }
366 |
367 | function prettyJSON(obj: any) {
368 | return JSON.stringify(obj, null, 2)
369 | }
370 |
371 | /**
372 | *
373 | * @param agentOptions - TODO
374 | * @returns
375 | */
376 | function getRuntimeOptions(
377 | agentOptions: AgentOptions | undefined
378 | ): functions.RuntimeOptions {
379 | const options: functions.RuntimeOptions = {
380 | // Always assign timeout and memory to runtime options. Unless the user
381 | // defines custom values, we want to use the default timeout of 9 minutes
382 | // and 1Gb memory to make sure the user backups are completed regardless
383 | // of how many there are.
384 | timeoutSeconds: agentOptions?.timeout || defaultTimeout,
385 | memory: agentOptions?.memory || defaultMemory,
386 | }
387 |
388 | if (agentOptions?.memory) options.memory = agentOptions.memory
389 |
390 | return options
391 | }
392 |
--------------------------------------------------------------------------------
/src/options.ts:
--------------------------------------------------------------------------------
1 | export const defaultControllerDomain = 'backupfire.dev'
2 |
3 | export const defaultRegion = 'us-central1'
4 |
5 | /**
6 | * The default function timeout - 9 minutes. It ensures that the user backups
7 | * are completed regardless of how many there are.
8 | *
9 | * Unlike the memory runtime option, timeout doesn't affect the function
10 | * instance price, so it's safe to set it to max.
11 | */
12 | export const defaultTimeout = 540
13 |
14 | /**
15 | * The default function memory. With the increased timeout, it ensures
16 | * the users' backup completion.
17 | *
18 | * Internal testing shows that 1GB is the sweet spot. It's still cheap to run
19 | * and gives room to process huge backups.
20 | */
21 | export const defaultMemory = '1GB'
22 |
--------------------------------------------------------------------------------
/src/storage/index.ts:
--------------------------------------------------------------------------------
1 | import asyncMiddleware from '../_lib/asyncMiddleware'
2 | import { Storage as CloudStorage, Bucket } from '@google-cloud/storage'
3 | import * as functions from 'firebase-functions'
4 |
5 | export interface StorageOptions {
6 | bucketsAllowlist?: string[]
7 | }
8 |
9 | export function storageListMiddleware({ bucketsAllowlist }: StorageOptions) {
10 | return asyncMiddleware(async (request, response) => {
11 | functions.logger.info('Requested the storage list', { bucketsAllowlist })
12 |
13 | const storage = new CloudStorage()
14 | const [buckets] = await storage.getBuckets()
15 | const storageList: Storage[] = buckets.map(bucketAsStorage)
16 |
17 | functions.logger.info('Responding with the storage list', { storageList })
18 |
19 | response.send(storageList)
20 | })
21 | }
22 |
23 | interface StorageRetentionData {
24 | removeOldBackups: boolean
25 | keepBackupsValue: number
26 | keepBackupsUnit: KeepBackupsUnit
27 | }
28 |
29 | type KeepBackupsUnit = 'years' | 'months' | 'days'
30 |
31 | export interface CreateStorageRequestBody
32 | extends Partial {
33 | storageId: string
34 | location: string
35 | storageClass?: 'standard' | 'nearline' | 'coldline' | 'archive'
36 | }
37 |
38 | export function createStorageMiddleware({ bucketsAllowlist }: StorageOptions) {
39 | return asyncMiddleware(async (request, response) => {
40 | const body = request.body as CreateStorageRequestBody
41 |
42 | functions.logger.info('Requested a bucket creation', {
43 | // NOTE: Do not ...body here to avoid logging sensitive data
44 | storageId: body.storageId,
45 | location: body.location,
46 | storageClass: body.storageClass,
47 | removeOldBackups: body.removeOldBackups,
48 | keepBackupsValue: body.keepBackupsValue,
49 | keepBackupsUnit: body.keepBackupsUnit,
50 | bucketsAllowlist,
51 | })
52 |
53 | const [bucket] = await new CloudStorage().createBucket(body.storageId, {
54 | [body.storageClass || 'nearline']: true,
55 | location: body.location,
56 | })
57 |
58 | functions.logger.info('Bucket created', { bucket: bucket.name })
59 |
60 | if (
61 | body.removeOldBackups &&
62 | body.keepBackupsValue &&
63 | body.keepBackupsUnit
64 | ) {
65 | const deleteAge = keepBackupInDays(
66 | body.keepBackupsValue,
67 | body.keepBackupsUnit
68 | )
69 |
70 | if (body.removeOldBackups) {
71 | functions.logger.info('Setting the bucket lifecycle', { deleteAge })
72 |
73 | await bucket.addLifecycleRule(
74 | {
75 | action: { type: 'Delete' },
76 | condition: { age: deleteAge },
77 | },
78 | { append: false }
79 | )
80 | } else {
81 | functions.logger.info('Clearing the bucket lifecycle')
82 |
83 | await bucket.setMetadata({ lifecycle: null })
84 | }
85 | }
86 |
87 | const [bucketData] = await bucket.get()
88 | const storage = bucketAsStorage(bucketData)
89 |
90 | functions.logger.info('Responding with the storage object', { storage })
91 |
92 | response.send(storage)
93 | })
94 | }
95 |
96 | export interface PasswordedStorageOptions extends StorageOptions {
97 | adminPassword: string
98 | }
99 |
100 | interface UpdateStorageRequestBody extends StorageRetentionData {
101 | password: string
102 | }
103 |
104 | export function updateStorageMiddleware({
105 | bucketsAllowlist,
106 | adminPassword,
107 | }: PasswordedStorageOptions) {
108 | return asyncMiddleware(async (request, response) => {
109 | // TODO: Validate the payload
110 | const storageId = request.params.storageId as string
111 | const body = request.body as UpdateStorageRequestBody
112 |
113 | functions.logger.info('Requested a bucket update', {
114 | // NOTE: Do not ...body here to avoid logging sensitive data
115 | removeOldBackups: body.removeOldBackups,
116 | keepBackupsValue: body.keepBackupsValue,
117 | keepBackupsUnit: body.keepBackupsUnit,
118 | bucketsAllowlist,
119 | })
120 |
121 | if (body.password !== adminPassword) {
122 | functions.logger.info(
123 | 'Admin password is incorrect, responding with an error'
124 | )
125 |
126 | response.status(403).send({ friendlyMessage: 'Wrong admin password' })
127 | return
128 | }
129 |
130 | const bucket = new CloudStorage().bucket(storageId)
131 | const deleteAge = keepBackupInDays(
132 | body.keepBackupsValue,
133 | body.keepBackupsUnit
134 | )
135 |
136 | if (body.removeOldBackups) {
137 | functions.logger.info('Setting the bucket lifecycle', { deleteAge })
138 |
139 | await bucket.addLifecycleRule(
140 | {
141 | action: { type: 'Delete' },
142 | condition: { age: deleteAge },
143 | },
144 | { append: false }
145 | )
146 | } else {
147 | functions.logger.info('Clearing the bucket lifecycle')
148 |
149 | await bucket.setMetadata({ lifecycle: null })
150 | }
151 |
152 | const [bucketData] = await bucket.get()
153 | const storage = bucketAsStorage(bucketData)
154 |
155 | functions.logger.info('Responding with the storage object', { storage })
156 |
157 | response.send(storage)
158 | })
159 | }
160 |
161 | export interface ListFilesRequestQuery {
162 | path: string
163 | maxResults: string | undefined
164 | startOffset: string | undefined
165 | endOffset: string | undefined
166 | prefix: string | undefined
167 | }
168 |
169 | export function listFilesMiddleware({ bucketsAllowlist }: StorageOptions) {
170 | return asyncMiddleware(async (request, response) => {
171 | const storageId = request.params.storageId as string
172 | const query = request.query as unknown as ListFilesRequestQuery
173 |
174 | functions.logger.info('Requested bucket files list', {
175 | // NOTE: Do not ...query here to avoid logging sensitive data
176 | storageId,
177 | path: query.path,
178 | maxResults: query.maxResults,
179 | startOffset: query.startOffset,
180 | endOffset: query.endOffset,
181 | prefix: query.prefix,
182 | bucketsAllowlist,
183 | })
184 |
185 | const bucket = new CloudStorage().bucket(storageId)
186 | const [files] = await bucket.getFiles({
187 | prefix: query.path,
188 | maxResults: query.maxResults ? parseInt(query.maxResults) : undefined,
189 | startOffset: query.startOffset,
190 | endOffset: query.endOffset,
191 | autoPaginate: !query.endOffset && !query.startOffset,
192 | })
193 |
194 | const result = files.map((file) => ({
195 | name: file.name,
196 | createdAt: file.metadata.timeCreated,
197 | updatedAt: file.metadata.updated,
198 | size: parseInt(file.metadata.size),
199 | contentType: file.metadata.contentType, // 'application/octet-stream'
200 | storageClass: file.metadata.storageClass, // 'STANDARD'
201 | storageClassUpdatedAt: new Date(file.metadata.timeStorageClassUpdated),
202 | etag: file.metadata.etag,
203 | }))
204 |
205 | response.send(result)
206 | })
207 | }
208 |
209 | function bucketAsStorage(bucket: Bucket): Storage {
210 | const {
211 | metadata: {
212 | name,
213 | id,
214 | location,
215 | storageClass,
216 | locationType,
217 | projectNumber,
218 | timeCreated,
219 | updated,
220 | lifecycle,
221 | },
222 | } = bucket
223 |
224 | return {
225 | name,
226 | id,
227 | location,
228 | projectNumber,
229 | storageClass,
230 | locationType,
231 | createdAt: new Date(timeCreated),
232 | updatedAt: new Date(updated),
233 | lifecycle: lifecycle && lifecycle.rule,
234 | }
235 | }
236 |
237 | export function keepBackupInDays(value: number, unit: KeepBackupsUnit) {
238 | // https://cloud.google.com/storage/docs/bucket-lock#retention-periods
239 | switch (unit) {
240 | case 'days':
241 | return value
242 |
243 | case 'months':
244 | return 31 * value
245 |
246 | case 'years':
247 | return Math.floor(365.25 * value)
248 | }
249 | }
250 |
251 | export interface Storage {
252 | name: string
253 | id: string
254 | location: StorageLocation
255 | storageClass: StorageClass
256 | locationType: StorageLocationType
257 | projectNumber: string
258 | createdAt: Date
259 | updatedAt: Date
260 | lifecycle: LifecycleRule[]
261 | }
262 |
263 | interface LifecycleRule {
264 | action: { type: string; storageClass?: string } | string
265 | condition: { [key: string]: boolean | Date | number | string }
266 | storageClass?: string
267 | }
268 |
269 | export type StorageLocation =
270 | | 'US'
271 | | 'EU'
272 | | 'ASIA'
273 | | 'NORTHAMERICA-NORTHEAST1'
274 | | 'EUR4'
275 | | 'NAM4'
276 | | 'US-CENTRAL1'
277 | | 'US-EAST1'
278 | | 'US-EAST4'
279 | | 'US-WEST1'
280 | | 'US-WEST2'
281 | | 'SOUTHAMERICA-EAST1'
282 | | 'EUROPE-NORTH1'
283 | | 'EUROPE-WEST1'
284 | | 'EUROPE-WEST2'
285 | | 'EUROPE-WEST3'
286 | | 'EUROPE-WEST4'
287 | | 'EUROPE-WEST6'
288 | | 'ASIA-EAST1'
289 | | 'ASIA-EAST2'
290 | | 'ASIA-NORTHEAST1'
291 | | 'ASIA-NORTHEAST2'
292 | | 'ASIA-SOUTH1'
293 | | 'ASIA-SOUTHEAST1'
294 | | 'AUSTRALIA-SOUTHEAST1'
295 |
296 | export type StorageClass =
297 | | 'STANDARD'
298 | | 'NEARLINE' /* min 30 days */
299 | | 'COLDLINE' /* min 90 days */
300 | | 'MULTI-REGIONAL'
301 | | 'REGIONAL'
302 | | 'DURABLE_REDUCED_AVAILABILITY'
303 |
304 | export type StorageLocationType = 'region' | 'dual-region' | 'multi-region'
305 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export type Region = typeof import('firebase-functions').region extends (
2 | region: infer RegionType
3 | ) => any
4 | ? RegionType
5 | : never
6 |
7 | export type Memory = typeof import('firebase-functions').VALID_MEMORY_OPTIONS[number]
8 |
9 | /**
10 | * Backup Fire agent options.
11 | */
12 | export interface BackupFireOptions {
13 | /**
14 | * The Google Cloud region id where to deploy the Firebase function.
15 | */
16 | region?: Region
17 |
18 | /**
19 | * The agent function memory limit, defaults to "256MB".
20 | */
21 | memory?: Memory
22 |
23 | /**
24 | * The agent function timeout in seconds, defaults to 60.
25 | */
26 | timeout?: number
27 |
28 | /**
29 | * The controller app domain, defaults to backupfire.dev.
30 | */
31 | controllerDomain?: string
32 |
33 | /**
34 | * The controller access token that allows to securely communicate with
35 | * the controller.
36 | */
37 | controllerToken: string
38 |
39 | /**
40 | * The admin password which protects the agent from unauthorized commands
41 | * from the controller.
42 | */
43 | adminPassword: string
44 |
45 | /**
46 | * The list of buckets where the data can be backed up. It protects the agent
47 | * from malformed backup commands from the controller.
48 | */
49 | bucketsAllowlist?: string[]
50 |
51 | /**
52 | * Make the agent print debug messages to the log.
53 | */
54 | debug?: boolean
55 | }
56 |
57 | // TODO: Split options definition to the ones coming from the environment config
58 | // and the user-defined agent options.
59 | export type AgentOptions = Pick<
60 | BackupFireOptions,
61 | 'region' | 'controllerDomain' | 'debug' | 'memory' | 'timeout'
62 | >
63 |
64 | export interface BackupFireEnvConfig {
65 | domain?: string
66 | token: string
67 | password: string
68 | allowlist?: string
69 | debug?: string
70 | }
71 |
72 | export interface RuntimeEnvironment {
73 | region: string
74 | projectId: string
75 | functionName: string
76 | extensionId: string | undefined
77 | runtime: string | undefined
78 | }
79 |
80 | export type BackupFireHTTPSHandler = (
81 | request: import('firebase-functions').Request,
82 | response: import('firebase-functions').Response
83 | ) => any
84 |
--------------------------------------------------------------------------------
/src/users/index.ts:
--------------------------------------------------------------------------------
1 | import * as admin from 'firebase-admin'
2 | import * as tools from 'firebase-tools'
3 | import fs from 'fs'
4 | import fetch from 'node-fetch'
5 | import { tmpdir } from 'os'
6 | import { parse, resolve } from 'path'
7 | import { format } from 'url'
8 | import { promisify } from 'util'
9 | import { defaultControllerDomain } from '../options'
10 | import asyncMiddleware from '../_lib/asyncMiddleware'
11 | import operationResponse, { UsersStatusResponse } from '../_lib/operation'
12 | import * as functions from 'firebase-functions'
13 |
14 | const unlink = promisify(fs.unlink)
15 |
16 | export interface UsersBackupOptions {
17 | bucketsAllowlist?: string[]
18 | projectId: string
19 | controllerDomain?: string
20 | controllerToken: string
21 | agentURL: string
22 | }
23 |
24 | export interface UsersBackupRequestBody {
25 | storageId: string
26 | path: string
27 | delay?: {
28 | backupId: string
29 | state: 'delay' | 'backup'
30 | }
31 | }
32 |
33 | export function backupUsersMiddleware({
34 | bucketsAllowlist,
35 | projectId,
36 | controllerDomain,
37 | controllerToken,
38 | agentURL,
39 | }: UsersBackupOptions) {
40 | return asyncMiddleware(async (request, response) => {
41 | // TODO: Validate options
42 | const body = request.body as UsersBackupRequestBody
43 |
44 | functions.logger.info('Requested users backup', {
45 | // NOTE: Do not ...body here to avoid logging sensitive data
46 | storageId: body.storageId,
47 | path: body.path,
48 | backupId: body.delay?.backupId,
49 | state: body.delay?.state,
50 | bucketsAllowlist,
51 | projectId,
52 | })
53 |
54 | if (body.delay?.state === 'delay') {
55 | functions.logger.info('Delaying users backup')
56 |
57 | // NOTE: Trigger backup, but do not wait for the result
58 | fetch(agentURL + request.path, {
59 | method: 'POST',
60 | headers: {
61 | // Authorization can't be missing as we verify it
62 | Authorization: request.header('Authorization')!,
63 | 'Content-Type': 'application/json',
64 | },
65 | body: JSON.stringify({
66 | storageId: body.storageId,
67 | path: body.path,
68 | delay: {
69 | state: 'backup',
70 | backupId: body.delay.backupId,
71 | },
72 | }),
73 | })
74 |
75 | functions.logger.info('Responding with pending status')
76 |
77 | operationResponse(response, { state: 'pending' })
78 | } else {
79 | functions.logger.info('Initiating users backup')
80 |
81 | const backupResponse = await backupUsers(projectId, body)
82 |
83 | functions.logger.info('Got the users backup response', {
84 | // NOTE: Do not ...body here to avoid logging sensitive data
85 | state: backupResponse.state,
86 | ...(backupResponse.state === 'pending'
87 | ? {}
88 | : backupResponse.state === 'completed'
89 | ? {
90 | usersCount: backupResponse.data.usersCount,
91 | size: backupResponse.data.size,
92 | }
93 | : {
94 | reason: backupResponse.data.reason,
95 | }),
96 | })
97 |
98 | if (body.delay) {
99 | const reportURL = format({
100 | hostname: controllerDomain || defaultControllerDomain,
101 | protocol: 'https',
102 | pathname: '/reportBackup',
103 | })
104 |
105 | functions.logger.info('Reporting users backup to the controller', {
106 | reportURL,
107 | })
108 |
109 | await fetch(reportURL, {
110 | method: 'POST',
111 | headers: { 'Content-Type': 'application/json' },
112 | body: JSON.stringify({
113 | token: controllerToken,
114 | backupId: body.delay.backupId,
115 | type: 'users',
116 | ...backupResponse,
117 | }),
118 | })
119 | }
120 |
121 | functions.logger.info('Responding with the backup status')
122 |
123 | operationResponse(response, backupResponse)
124 | }
125 | })
126 | }
127 |
128 | async function backupUsers(
129 | projectId: string,
130 | options: UsersBackupRequestBody
131 | ): Promise {
132 | // Create bucket
133 | const bucket = admin.storage().bucket(options.storageId)
134 |
135 | // Create temporary file path
136 | const path = tmpPath(options.path)
137 |
138 | functions.logger.info('Exporting users to a temporary file', { path })
139 |
140 | // Export users to a temporary file
141 | await tools.auth.export(path, { project: projectId })
142 |
143 | // Calculate users in the backup and
144 | // upload the users backup to the storage
145 |
146 | functions.logger.info(
147 | 'Uploading users backup to the storage and counting the users'
148 | )
149 |
150 | const [usersCount, size] = await Promise.all([
151 | calculateUsers(path).then((count) => {
152 | functions.logger.info('Got the users count', { count })
153 | return count
154 | }),
155 |
156 | bucket
157 | .upload(path, { destination: options.path })
158 | .then(([file]) => file.metadata.size as string)
159 | .then((size) => {
160 | functions.logger.info('Uploaded the users backup to the storage', {
161 | size,
162 | })
163 | return size
164 | }),
165 | ])
166 |
167 | functions.logger.info('Removing the temporary file')
168 |
169 | // Remove the temporary file
170 | await unlink(path)
171 |
172 | return { state: 'completed', data: { usersCount, size } }
173 | }
174 |
175 | /**
176 | * Calculates the number of users in the backup.
177 | * @param path - the backup path
178 | * @returns the number of users in the backup
179 | */
180 | export async function calculateUsers(path: string) {
181 | let usersCount = 0
182 | let lookingForEnding: string | null = null
183 |
184 | for await (const chunk of generateFileChunks(path, 10000000 /* 10MB */)) {
185 | const text: string = chunk.toString()
186 |
187 | if (lookingForEnding) {
188 | if (text.slice(0, lookingForEnding.length) === lookingForEnding)
189 | usersCount++
190 | lookingForEnding = null
191 | }
192 |
193 | usersCount += text.match(/"localId"/g)?.length || 0
194 |
195 | const ending = text.match(/"(l(o(c(a(l(I(d(")?)?)?)?)?)?)?)?$/)
196 | if (ending) lookingForEnding = '"localId"'.slice(ending[0].length)
197 | }
198 |
199 | return usersCount
200 | }
201 |
202 | /**
203 | * Calculates the number of users in the file stream.
204 | * @param usersStream - the backup file stream
205 | * @returns the number of users in the stream
206 | */
207 | export async function calculateUsersInSteam(usersStream: fs.ReadStream) {
208 | let usersCount = 0
209 | let lookingForEnding: string | null = null
210 |
211 | for await (const data of usersStream) {
212 | const text: string = data.toString()
213 |
214 | if (lookingForEnding) {
215 | if (text.slice(0, lookingForEnding.length) === lookingForEnding)
216 | usersCount++
217 | lookingForEnding = null
218 | }
219 |
220 | usersCount += text.match(/"localId"/g)?.length || 0
221 |
222 | const ending = text.match(/"(l(o(c(a(l(I(d(")?)?)?)?)?)?)?)?$/)
223 | if (ending) lookingForEnding = '"localId"'.slice(ending[0].length)
224 | }
225 |
226 | return usersCount
227 | }
228 |
229 | /**
230 | * Genenerates chunks of data from the given file.
231 | *
232 | * The code is based on the article by Kasper Moskwiak (https://github.com/kmoskwiak): https://betterprogramming.pub/a-memory-friendly-way-of-reading-files-in-node-js-a45ad0cc7bb6
233 | *
234 | * @param path - the file path to read
235 | * @param size - the chunk size
236 | */
237 | async function* generateFileChunks(path: string, size: number) {
238 | const sharedBuffer = Buffer.alloc(size)
239 | const stats = fs.statSync(path)
240 | const file = fs.openSync(path, 'r')
241 |
242 | let bytesRead = 0 // How many bytes were read
243 | let end = size
244 |
245 | for (let chunk = 0; chunk < Math.ceil(stats.size / size); chunk++) {
246 | await readFileBytes(file, sharedBuffer)
247 |
248 | bytesRead = (chunk + 1) * size
249 | // When we reach the end of file, we have to calculate how many bytes were
250 | // actually read.
251 | if (bytesRead > stats.size) end = size - (bytesRead - stats.size)
252 |
253 | yield sharedBuffer.slice(0, end)
254 | }
255 | }
256 |
257 | /**
258 | * Reads the file bytes into the shared buffer.
259 | *
260 | * The code is based on the article by Kasper Moskwiak (https://github.com/kmoskwiak): https://betterprogramming.pub/a-memory-friendly-way-of-reading-files-in-node-js-a45ad0cc7bb6
261 | *
262 | * @param file - the file descriptor
263 | * @param buffer - the shared buffer to use
264 | * @returns promise to file read
265 | */
266 | function readFileBytes(file: number, buffer: Buffer) {
267 | return new Promise((resolve, reject) => {
268 | fs.read(file, buffer, 0, buffer.length, null, (error) => {
269 | if (error) return reject(error)
270 | resolve(void 0)
271 | })
272 | })
273 | }
274 |
275 | /**
276 | * Generates temporary path on the FS from passed path in a bucket
277 | * @param path - The path to backup in a bucket
278 | */
279 | function tmpPath(path: string) {
280 | const { base: fileName } = parse(path)
281 | return resolve(tmpdir(), fileName)
282 | }
283 |
--------------------------------------------------------------------------------
/src/users/test.ts:
--------------------------------------------------------------------------------
1 | import { writeFile } from 'fs/promises'
2 | import { fs } from 'mz'
3 | import { tmpdir } from 'os'
4 | import { resolve } from 'path'
5 | import { calculateUsers, calculateUsersInSteam } from '.'
6 |
7 | describe('users', () => {
8 | describe('calculateUsers', () => {
9 | let backupPath: string
10 | beforeEach(() => {
11 | backupPath = resolve(tmpdir(), Date.now() + '.json')
12 | return writeFile(
13 | backupPath,
14 | JSON.stringify([{ localId: 1 }, { localId: 2 }, { localId: 3 }])
15 | )
16 | })
17 |
18 | it('calculates the number of users in the backup', async () => {
19 | const result = await calculateUsers(backupPath)
20 | expect(result).toBe(3)
21 | })
22 | })
23 |
24 | describe('calculateUsersInSteam', () => {
25 | it('calculates the number of users in the stream', async () => {
26 | const stream = ([
27 | '[{"localId":1},{',
28 | '"localId":2},{"localId":3}]'
29 | ] as unknown) as fs.ReadStream
30 | const result = await calculateUsersInSteam(stream)
31 | expect(result).toBe(3)
32 | })
33 |
34 | it('considers chunks split', async () => {
35 | const stream = ([
36 | '[{"localId":1},{',
37 | '"loca',
38 | 'lId":2},{"localId":3}]'
39 | ] as unknown) as fs.ReadStream
40 | const result = await calculateUsersInSteam(stream)
41 | expect(result).toBe(3)
42 | })
43 |
44 | it('skips incomplete symbols when calculating split', async () => {
45 | const stream = ([
46 | '[{"localId":1},{',
47 | '"loca',
48 | // It must be 'lId":2},{"localId":3}]'
49 | // to calculate split
50 | 'Id":2},{"localId":3}]'
51 | ] as unknown) as fs.ReadStream
52 | const result = await calculateUsersInSteam(stream)
53 | expect(result).toBe(2)
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/src/version.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | let version = 'dev'
5 | try {
6 | const packageStr = fs.readFileSync(
7 | path.resolve(__dirname, './package.json'),
8 | 'utf8'
9 | )
10 | const packageJson = JSON.parse(packageStr)
11 | if (typeof packageJson.version === 'string')
12 | version = `v${packageJson.version}`
13 | } catch (_err) {}
14 |
15 | export default version
16 |
--------------------------------------------------------------------------------
/test/extension/.firebaserc.example:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "backup-fire-playground-ext"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/extension/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "source": "."
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/extension/index.ts:
--------------------------------------------------------------------------------
1 | import * as functions from 'firebase-functions'
2 |
3 | export const helloWorld = functions.https.onRequest((_request, response) => {
4 | response.send('Hello from Firebase!')
5 | })
6 |
--------------------------------------------------------------------------------
/test/extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backupfire-firebase-test-server-ext",
3 | "version": "1.0.0",
4 | "description": "Backup Fire test server",
5 | "main": "./build/index.js",
6 | "author": "Sasha Koss ",
7 | "license": "MIT",
8 | "engines": {
9 | "node": "14"
10 | },
11 | "dependencies": {
12 | "firebase-admin": "^9.6.0",
13 | "firebase-functions": "^3.13.2"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/extension/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["test/extension/**/*.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/test/extension/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@firebase/app-types@0.6.2":
6 | version "0.6.2"
7 | resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.2.tgz#8578cb1061a83ced4570188be9e225d54e0f27fb"
8 | integrity sha512-2VXvq/K+n8XMdM4L2xy5bYp2ZXMawJXluUIDzUBvMthVR+lhxK4pfFiqr1mmDbv9ydXvEAuFsD+6DpcZuJcSSw==
9 |
10 | "@firebase/auth-interop-types@0.1.6":
11 | version "0.1.6"
12 | resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz#5ce13fc1c527ad36f1bb1322c4492680a6cf4964"
13 | integrity sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==
14 |
15 | "@firebase/component@0.5.0":
16 | version "0.5.0"
17 | resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.5.0.tgz#f5b577d6c6f78d0f12fdc45046108921507f49c9"
18 | integrity sha512-v18csWtXb0ri+3m7wuGLY/UDgcb89vuMlZGQ//+7jEPLIQeLbylvZhol1uzW9WzoOpxMxOS2W5qyVGX36wZvEA==
19 | dependencies:
20 | "@firebase/util" "1.1.0"
21 | tslib "^2.1.0"
22 |
23 | "@firebase/database-types@0.7.2", "@firebase/database-types@^0.7.2":
24 | version "0.7.2"
25 | resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.7.2.tgz#449c4b36ec59a1ad9089797b540e2ba1c0d4fcbf"
26 | integrity sha512-cdAd/dgwvC0r3oLEDUR+ULs1vBsEvy0b27nlzKhU6LQgm9fCDzgaH9nFGv8x+S9dly4B0egAXkONkVoWcOAisg==
27 | dependencies:
28 | "@firebase/app-types" "0.6.2"
29 |
30 | "@firebase/database@^0.10.0":
31 | version "0.10.1"
32 | resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.10.1.tgz#1e8c64519552f225a4a88e7dae09ecf29447f2be"
33 | integrity sha512-umT0kynJKc5VpVBOg3+YTDzdJORssh+QqPjoHfbSvtmgZizNiV8mgmKRcDhlVM6CisPb6v5xBn9l8JbK/WRQ1Q==
34 | dependencies:
35 | "@firebase/auth-interop-types" "0.1.6"
36 | "@firebase/component" "0.5.0"
37 | "@firebase/database-types" "0.7.2"
38 | "@firebase/logger" "0.2.6"
39 | "@firebase/util" "1.1.0"
40 | faye-websocket "0.11.3"
41 | tslib "^2.1.0"
42 |
43 | "@firebase/logger@0.2.6":
44 | version "0.2.6"
45 | resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.2.6.tgz#3aa2ca4fe10327cabf7808bd3994e88db26d7989"
46 | integrity sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==
47 |
48 | "@firebase/util@1.1.0":
49 | version "1.1.0"
50 | resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.1.0.tgz#add2d57d0b2307a932520abdee303b66be0ac8b0"
51 | integrity sha512-lfuSASuPKNdfebuFR8rjFamMQUPH9iiZHcKS755Rkm/5gRT0qC7BMhCh3ZkHf7NVbplzIc/GhmX2jM+igDRCag==
52 | dependencies:
53 | tslib "^2.1.0"
54 |
55 | "@google-cloud/common@^3.6.0":
56 | version "3.6.0"
57 | resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-3.6.0.tgz#c2f6da5f79279a4a9ac7c71fc02d582beab98e8b"
58 | integrity sha512-aHIFTqJZmeTNO9md8XxV+ywuvXF3xBm5WNmgWeeCK+XN5X+kGW0WEX94wGwj+/MdOnrVf4dL2RvSIt9J5yJG6Q==
59 | dependencies:
60 | "@google-cloud/projectify" "^2.0.0"
61 | "@google-cloud/promisify" "^2.0.0"
62 | arrify "^2.0.1"
63 | duplexify "^4.1.1"
64 | ent "^2.2.0"
65 | extend "^3.0.2"
66 | google-auth-library "^7.0.2"
67 | retry-request "^4.1.1"
68 | teeny-request "^7.0.0"
69 |
70 | "@google-cloud/firestore@^4.5.0":
71 | version "4.11.1"
72 | resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-4.11.1.tgz#03e12e5721383165efc09e208143378f3ea681d6"
73 | integrity sha512-iNsCGYwKBxYZS+TpkUAJLGkGko2QtWaf11JDNx6kvqOVN0359qSnZlF1SWFTvm26ZsKyX6uR4oAiFmmjfXTlCg==
74 | dependencies:
75 | fast-deep-equal "^3.1.1"
76 | functional-red-black-tree "^1.0.1"
77 | google-gax "^2.12.0"
78 | protobufjs "^6.8.6"
79 |
80 | "@google-cloud/paginator@^3.0.0":
81 | version "3.0.5"
82 | resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-3.0.5.tgz#9d6b96c421a89bd560c1bc2c197c7611ef21db6c"
83 | integrity sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==
84 | dependencies:
85 | arrify "^2.0.0"
86 | extend "^3.0.2"
87 |
88 | "@google-cloud/projectify@^2.0.0":
89 | version "2.0.1"
90 | resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-2.0.1.tgz#13350ee609346435c795bbfe133a08dfeab78d65"
91 | integrity sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==
92 |
93 | "@google-cloud/promisify@^2.0.0":
94 | version "2.0.3"
95 | resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-2.0.3.tgz#f934b5cdc939e3c7039ff62b9caaf59a9d89e3a8"
96 | integrity sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==
97 |
98 | "@google-cloud/storage@^5.3.0":
99 | version "5.8.5"
100 | resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-5.8.5.tgz#2cf1e2e0ef8ca552abc4450301fef3fea4900ef6"
101 | integrity sha512-i0gB9CRwQeOBYP7xuvn14M40LhHCwMjceBjxE4CTvsqL519sVY5yVKxLiAedHWGwUZHJNRa7Q2CmNfkdRwVNPg==
102 | dependencies:
103 | "@google-cloud/common" "^3.6.0"
104 | "@google-cloud/paginator" "^3.0.0"
105 | "@google-cloud/promisify" "^2.0.0"
106 | arrify "^2.0.0"
107 | async-retry "^1.3.1"
108 | compressible "^2.0.12"
109 | date-and-time "^1.0.0"
110 | duplexify "^4.0.0"
111 | extend "^3.0.2"
112 | gaxios "^4.0.0"
113 | gcs-resumable-upload "^3.1.4"
114 | get-stream "^6.0.0"
115 | hash-stream-validation "^0.2.2"
116 | mime "^2.2.0"
117 | mime-types "^2.0.8"
118 | onetime "^5.1.0"
119 | p-limit "^3.0.1"
120 | pumpify "^2.0.0"
121 | snakeize "^0.1.0"
122 | stream-events "^1.0.1"
123 | xdg-basedir "^4.0.0"
124 |
125 | "@grpc/grpc-js@~1.3.0":
126 | version "1.3.2"
127 | resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.2.tgz#eae97e6daf5abd49a7818aadeca0744dfb1ebca1"
128 | integrity sha512-UXepkOKCATJrhHGsxt+CGfpZy9zUn1q9mop5kfcXq1fBkTePxVNPOdnISlCbJFlCtld+pSLGyZCzr9/zVprFKA==
129 | dependencies:
130 | "@types/node" ">=12.12.47"
131 |
132 | "@grpc/proto-loader@^0.6.1":
133 | version "0.6.2"
134 | resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.2.tgz#412575f3ff5ef0a9b79d4ea12c08cba5601041cb"
135 | integrity sha512-q2Qle60Ht2OQBCp9S5hv1JbI4uBBq6/mqSevFNK3ZEgRDBCAkWqZPUhD/K9gXOHrHKluliHiVq2L9sw1mVyAIg==
136 | dependencies:
137 | "@types/long" "^4.0.1"
138 | lodash.camelcase "^4.3.0"
139 | long "^4.0.0"
140 | protobufjs "^6.10.0"
141 | yargs "^16.1.1"
142 |
143 | "@panva/asn1.js@^1.0.0":
144 | version "1.0.0"
145 | resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6"
146 | integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==
147 |
148 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
149 | version "1.1.2"
150 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
151 | integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
152 |
153 | "@protobufjs/base64@^1.1.2":
154 | version "1.1.2"
155 | resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
156 | integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
157 |
158 | "@protobufjs/codegen@^2.0.4":
159 | version "2.0.4"
160 | resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
161 | integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
162 |
163 | "@protobufjs/eventemitter@^1.1.0":
164 | version "1.1.0"
165 | resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
166 | integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
167 |
168 | "@protobufjs/fetch@^1.1.0":
169 | version "1.1.0"
170 | resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
171 | integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
172 | dependencies:
173 | "@protobufjs/aspromise" "^1.1.1"
174 | "@protobufjs/inquire" "^1.1.0"
175 |
176 | "@protobufjs/float@^1.0.2":
177 | version "1.0.2"
178 | resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
179 | integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
180 |
181 | "@protobufjs/inquire@^1.1.0":
182 | version "1.1.0"
183 | resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
184 | integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
185 |
186 | "@protobufjs/path@^1.1.2":
187 | version "1.1.2"
188 | resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
189 | integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
190 |
191 | "@protobufjs/pool@^1.1.0":
192 | version "1.1.0"
193 | resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
194 | integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
195 |
196 | "@protobufjs/utf8@^1.1.0":
197 | version "1.1.0"
198 | resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
199 | integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
200 |
201 | "@tootallnate/once@1":
202 | version "1.1.2"
203 | resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
204 | integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
205 |
206 | "@types/body-parser@*":
207 | version "1.19.0"
208 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
209 | integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
210 | dependencies:
211 | "@types/connect" "*"
212 | "@types/node" "*"
213 |
214 | "@types/connect@*":
215 | version "3.4.34"
216 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901"
217 | integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==
218 | dependencies:
219 | "@types/node" "*"
220 |
221 | "@types/express-jwt@0.0.42":
222 | version "0.0.42"
223 | resolved "https://registry.yarnpkg.com/@types/express-jwt/-/express-jwt-0.0.42.tgz#4f04e1fadf9d18725950dc041808a4a4adf7f5ae"
224 | integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==
225 | dependencies:
226 | "@types/express" "*"
227 | "@types/express-unless" "*"
228 |
229 | "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18":
230 | version "4.17.19"
231 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d"
232 | integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==
233 | dependencies:
234 | "@types/node" "*"
235 | "@types/qs" "*"
236 | "@types/range-parser" "*"
237 |
238 | "@types/express-unless@*":
239 | version "0.5.1"
240 | resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.5.1.tgz#4f440b905e42bbf53382b8207bc337dc5ff9fd1f"
241 | integrity sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==
242 | dependencies:
243 | "@types/express" "*"
244 |
245 | "@types/express@*":
246 | version "4.17.11"
247 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545"
248 | integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==
249 | dependencies:
250 | "@types/body-parser" "*"
251 | "@types/express-serve-static-core" "^4.17.18"
252 | "@types/qs" "*"
253 | "@types/serve-static" "*"
254 |
255 | "@types/express@4.17.3":
256 | version "4.17.3"
257 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9"
258 | integrity sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==
259 | dependencies:
260 | "@types/body-parser" "*"
261 | "@types/express-serve-static-core" "*"
262 | "@types/serve-static" "*"
263 |
264 | "@types/long@^4.0.0", "@types/long@^4.0.1":
265 | version "4.0.1"
266 | resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
267 | integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
268 |
269 | "@types/mime@^1":
270 | version "1.3.2"
271 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
272 | integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
273 |
274 | "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
275 | version "15.3.0"
276 | resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26"
277 | integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==
278 |
279 | "@types/qs@*":
280 | version "6.9.6"
281 | resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1"
282 | integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==
283 |
284 | "@types/range-parser@*":
285 | version "1.2.3"
286 | resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
287 | integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
288 |
289 | "@types/serve-static@*":
290 | version "1.13.9"
291 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e"
292 | integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==
293 | dependencies:
294 | "@types/mime" "^1"
295 | "@types/node" "*"
296 |
297 | abort-controller@^3.0.0:
298 | version "3.0.0"
299 | resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
300 | integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
301 | dependencies:
302 | event-target-shim "^5.0.0"
303 |
304 | accepts@~1.3.7:
305 | version "1.3.7"
306 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
307 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
308 | dependencies:
309 | mime-types "~2.1.24"
310 | negotiator "0.6.2"
311 |
312 | agent-base@6:
313 | version "6.0.2"
314 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
315 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
316 | dependencies:
317 | debug "4"
318 |
319 | ansi-regex@^5.0.0:
320 | version "5.0.0"
321 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
322 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
323 |
324 | ansi-styles@^4.0.0:
325 | version "4.3.0"
326 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
327 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
328 | dependencies:
329 | color-convert "^2.0.1"
330 |
331 | array-flatten@1.1.1:
332 | version "1.1.1"
333 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
334 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
335 |
336 | arrify@^2.0.0, arrify@^2.0.1:
337 | version "2.0.1"
338 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
339 | integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
340 |
341 | async-retry@^1.3.1:
342 | version "1.3.1"
343 | resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55"
344 | integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==
345 | dependencies:
346 | retry "0.12.0"
347 |
348 | base64-js@^1.3.0:
349 | version "1.5.1"
350 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
351 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
352 |
353 | bignumber.js@^9.0.0:
354 | version "9.0.1"
355 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
356 | integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==
357 |
358 | body-parser@1.19.0:
359 | version "1.19.0"
360 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
361 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
362 | dependencies:
363 | bytes "3.1.0"
364 | content-type "~1.0.4"
365 | debug "2.6.9"
366 | depd "~1.1.2"
367 | http-errors "1.7.2"
368 | iconv-lite "0.4.24"
369 | on-finished "~2.3.0"
370 | qs "6.7.0"
371 | raw-body "2.4.0"
372 | type-is "~1.6.17"
373 |
374 | buffer-equal-constant-time@1.0.1:
375 | version "1.0.1"
376 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
377 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
378 |
379 | bytes@3.1.0:
380 | version "3.1.0"
381 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
382 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
383 |
384 | cliui@^7.0.2:
385 | version "7.0.4"
386 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
387 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
388 | dependencies:
389 | string-width "^4.2.0"
390 | strip-ansi "^6.0.0"
391 | wrap-ansi "^7.0.0"
392 |
393 | color-convert@^2.0.1:
394 | version "2.0.1"
395 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
396 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
397 | dependencies:
398 | color-name "~1.1.4"
399 |
400 | color-name@~1.1.4:
401 | version "1.1.4"
402 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
403 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
404 |
405 | compressible@^2.0.12:
406 | version "2.0.18"
407 | resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
408 | integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
409 | dependencies:
410 | mime-db ">= 1.43.0 < 2"
411 |
412 | configstore@^5.0.0:
413 | version "5.0.1"
414 | resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
415 | integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
416 | dependencies:
417 | dot-prop "^5.2.0"
418 | graceful-fs "^4.1.2"
419 | make-dir "^3.0.0"
420 | unique-string "^2.0.0"
421 | write-file-atomic "^3.0.0"
422 | xdg-basedir "^4.0.0"
423 |
424 | content-disposition@0.5.3:
425 | version "0.5.3"
426 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
427 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
428 | dependencies:
429 | safe-buffer "5.1.2"
430 |
431 | content-type@~1.0.4:
432 | version "1.0.4"
433 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
434 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
435 |
436 | cookie-signature@1.0.6:
437 | version "1.0.6"
438 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
439 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
440 |
441 | cookie@0.4.0:
442 | version "0.4.0"
443 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
444 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
445 |
446 | cors@^2.8.5:
447 | version "2.8.5"
448 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
449 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
450 | dependencies:
451 | object-assign "^4"
452 | vary "^1"
453 |
454 | crypto-random-string@^2.0.0:
455 | version "2.0.0"
456 | resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
457 | integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
458 |
459 | date-and-time@^1.0.0:
460 | version "1.0.0"
461 | resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-1.0.0.tgz#0062394bdf6f44e961f0db00511cb19cdf3cc0a5"
462 | integrity sha512-477D7ypIiqlXBkxhU7YtG9wWZJEQ+RUpujt2quTfgf4+E8g5fNUkB0QIL0bVyP5/TKBg8y55Hfa1R/c4bt3dEw==
463 |
464 | debug@2.6.9:
465 | version "2.6.9"
466 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
467 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
468 | dependencies:
469 | ms "2.0.0"
470 |
471 | debug@4, debug@^4.1.0, debug@^4.1.1:
472 | version "4.3.1"
473 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
474 | integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
475 | dependencies:
476 | ms "2.1.2"
477 |
478 | depd@~1.1.2:
479 | version "1.1.2"
480 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
481 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
482 |
483 | destroy@~1.0.4:
484 | version "1.0.4"
485 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
486 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
487 |
488 | dicer@^0.3.0:
489 | version "0.3.0"
490 | resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
491 | integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==
492 | dependencies:
493 | streamsearch "0.1.2"
494 |
495 | dot-prop@^5.2.0:
496 | version "5.3.0"
497 | resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
498 | integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
499 | dependencies:
500 | is-obj "^2.0.0"
501 |
502 | duplexify@^4.0.0, duplexify@^4.1.1:
503 | version "4.1.1"
504 | resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.1.tgz#7027dc374f157b122a8ae08c2d3ea4d2d953aa61"
505 | integrity sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==
506 | dependencies:
507 | end-of-stream "^1.4.1"
508 | inherits "^2.0.3"
509 | readable-stream "^3.1.1"
510 | stream-shift "^1.0.0"
511 |
512 | ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
513 | version "1.0.11"
514 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
515 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
516 | dependencies:
517 | safe-buffer "^5.0.1"
518 |
519 | ee-first@1.1.1:
520 | version "1.1.1"
521 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
522 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
523 |
524 | emoji-regex@^8.0.0:
525 | version "8.0.0"
526 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
527 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
528 |
529 | encodeurl@~1.0.2:
530 | version "1.0.2"
531 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
532 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
533 |
534 | end-of-stream@^1.1.0, end-of-stream@^1.4.1:
535 | version "1.4.4"
536 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
537 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
538 | dependencies:
539 | once "^1.4.0"
540 |
541 | ent@^2.2.0:
542 | version "2.2.0"
543 | resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
544 | integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0=
545 |
546 | escalade@^3.1.1:
547 | version "3.1.1"
548 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
549 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
550 |
551 | escape-html@~1.0.3:
552 | version "1.0.3"
553 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
554 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
555 |
556 | etag@~1.8.1:
557 | version "1.8.1"
558 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
559 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
560 |
561 | event-target-shim@^5.0.0:
562 | version "5.0.1"
563 | resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
564 | integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
565 |
566 | express@^4.17.1:
567 | version "4.17.1"
568 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
569 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
570 | dependencies:
571 | accepts "~1.3.7"
572 | array-flatten "1.1.1"
573 | body-parser "1.19.0"
574 | content-disposition "0.5.3"
575 | content-type "~1.0.4"
576 | cookie "0.4.0"
577 | cookie-signature "1.0.6"
578 | debug "2.6.9"
579 | depd "~1.1.2"
580 | encodeurl "~1.0.2"
581 | escape-html "~1.0.3"
582 | etag "~1.8.1"
583 | finalhandler "~1.1.2"
584 | fresh "0.5.2"
585 | merge-descriptors "1.0.1"
586 | methods "~1.1.2"
587 | on-finished "~2.3.0"
588 | parseurl "~1.3.3"
589 | path-to-regexp "0.1.7"
590 | proxy-addr "~2.0.5"
591 | qs "6.7.0"
592 | range-parser "~1.2.1"
593 | safe-buffer "5.1.2"
594 | send "0.17.1"
595 | serve-static "1.14.1"
596 | setprototypeof "1.1.1"
597 | statuses "~1.5.0"
598 | type-is "~1.6.18"
599 | utils-merge "1.0.1"
600 | vary "~1.1.2"
601 |
602 | extend@^3.0.2:
603 | version "3.0.2"
604 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
605 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
606 |
607 | fast-deep-equal@^3.1.1:
608 | version "3.1.3"
609 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
610 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
611 |
612 | fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3:
613 | version "1.0.3"
614 | resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
615 | integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
616 |
617 | faye-websocket@0.11.3:
618 | version "0.11.3"
619 | resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
620 | integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
621 | dependencies:
622 | websocket-driver ">=0.5.1"
623 |
624 | finalhandler@~1.1.2:
625 | version "1.1.2"
626 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
627 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
628 | dependencies:
629 | debug "2.6.9"
630 | encodeurl "~1.0.2"
631 | escape-html "~1.0.3"
632 | on-finished "~2.3.0"
633 | parseurl "~1.3.3"
634 | statuses "~1.5.0"
635 | unpipe "~1.0.0"
636 |
637 | firebase-admin@^9.6.0:
638 | version "9.8.0"
639 | resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-9.8.0.tgz#d54d1acdd2e1117e59b7d74cb467ef3d18f3aa7a"
640 | integrity sha512-v8B1qU8McZZT2hlLZ018TKz2FoKlfFkZq9mOIyzN7wJUOlAywqQX0JyqNpVGyPeU+B+77ojlvmkGTNXt2OFkgw==
641 | dependencies:
642 | "@firebase/database" "^0.10.0"
643 | "@firebase/database-types" "^0.7.2"
644 | "@types/node" ">=12.12.47"
645 | dicer "^0.3.0"
646 | jsonwebtoken "^8.5.1"
647 | jwks-rsa "^2.0.2"
648 | node-forge "^0.10.0"
649 | optionalDependencies:
650 | "@google-cloud/firestore" "^4.5.0"
651 | "@google-cloud/storage" "^5.3.0"
652 |
653 | firebase-functions@^3.13.2:
654 | version "3.14.1"
655 | resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-3.14.1.tgz#3ac5bc70989365874f41d06bca3b42a233dd6039"
656 | integrity sha512-hL/qm+i5i1qKYmAFMlQ4mwRngDkP+3YT3F4E4Nd5Hj2QKeawBdZiMGgEt6zqTx08Zq04vHiSnSM0z75UJRSg6Q==
657 | dependencies:
658 | "@types/express" "4.17.3"
659 | cors "^2.8.5"
660 | express "^4.17.1"
661 | lodash "^4.17.14"
662 |
663 | forwarded@~0.1.2:
664 | version "0.1.2"
665 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
666 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
667 |
668 | fresh@0.5.2:
669 | version "0.5.2"
670 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
671 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
672 |
673 | functional-red-black-tree@^1.0.1:
674 | version "1.0.1"
675 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
676 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
677 |
678 | gaxios@^4.0.0:
679 | version "4.2.1"
680 | resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.2.1.tgz#7463d3a06f56ddbffa745a242d2b4933b88b2ada"
681 | integrity sha512-s+rTywpw6CmfB8r9TXYkpix7YFeuRjnR/AqhaJrQqsNhsAqej+IAiCc3hadzQH3gHyWth30tvYjxH8EVjQt/8Q==
682 | dependencies:
683 | abort-controller "^3.0.0"
684 | extend "^3.0.2"
685 | https-proxy-agent "^5.0.0"
686 | is-stream "^2.0.0"
687 | node-fetch "^2.3.0"
688 |
689 | gcp-metadata@^4.2.0:
690 | version "4.2.1"
691 | resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-4.2.1.tgz#31849fbcf9025ef34c2297c32a89a1e7e9f2cd62"
692 | integrity sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==
693 | dependencies:
694 | gaxios "^4.0.0"
695 | json-bigint "^1.0.0"
696 |
697 | gcs-resumable-upload@^3.1.4:
698 | version "3.1.4"
699 | resolved "https://registry.yarnpkg.com/gcs-resumable-upload/-/gcs-resumable-upload-3.1.4.tgz#2e591889efb02247af26868de300b398346b17b5"
700 | integrity sha512-5dyDfHrrVcIskiw/cPssVD4HRiwoHjhk1Nd6h5W3pQ/qffDvhfy4oNCr1f3ZXFPwTnxkCbibsB+73oOM+NvmJQ==
701 | dependencies:
702 | abort-controller "^3.0.0"
703 | configstore "^5.0.0"
704 | extend "^3.0.2"
705 | gaxios "^4.0.0"
706 | google-auth-library "^7.0.0"
707 | pumpify "^2.0.0"
708 | stream-events "^1.0.4"
709 |
710 | get-caller-file@^2.0.5:
711 | version "2.0.5"
712 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
713 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
714 |
715 | get-stream@^6.0.0:
716 | version "6.0.1"
717 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
718 | integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
719 |
720 | google-auth-library@^7.0.0, google-auth-library@^7.0.2:
721 | version "7.0.4"
722 | resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-7.0.4.tgz#610cb010de71435dca47dfbe8dc7fbff23055d2c"
723 | integrity sha512-o8irYyeijEiecTXeoEe8UKNEzV1X+uhR4b2oNdapDMZixypp0J+eHimGOyx5Joa3UAeokGngdtDLXtq9vDqG2Q==
724 | dependencies:
725 | arrify "^2.0.0"
726 | base64-js "^1.3.0"
727 | ecdsa-sig-formatter "^1.0.11"
728 | fast-text-encoding "^1.0.0"
729 | gaxios "^4.0.0"
730 | gcp-metadata "^4.2.0"
731 | gtoken "^5.0.4"
732 | jws "^4.0.0"
733 | lru-cache "^6.0.0"
734 |
735 | google-gax@^2.12.0:
736 | version "2.13.0"
737 | resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-2.13.0.tgz#404bb9df62c3a0a414e2f5339eda4d751f540304"
738 | integrity sha512-aKNJy2+Vv2I7flyNYbwpq0aYBHp6Qv32HZn+wr6ZhZ8xlSCLS9K9k7izfh2nd1rCJQcsqB6KMxHV0Vwny6Rc1g==
739 | dependencies:
740 | "@grpc/grpc-js" "~1.3.0"
741 | "@grpc/proto-loader" "^0.6.1"
742 | "@types/long" "^4.0.0"
743 | abort-controller "^3.0.0"
744 | duplexify "^4.0.0"
745 | fast-text-encoding "^1.0.3"
746 | google-auth-library "^7.0.2"
747 | is-stream-ended "^0.1.4"
748 | node-fetch "^2.6.1"
749 | object-hash "^2.1.1"
750 | protobufjs "^6.10.2"
751 | retry-request "^4.0.0"
752 |
753 | google-p12-pem@^3.0.3:
754 | version "3.0.3"
755 | resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e"
756 | integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==
757 | dependencies:
758 | node-forge "^0.10.0"
759 |
760 | graceful-fs@^4.1.2:
761 | version "4.2.6"
762 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
763 | integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
764 |
765 | gtoken@^5.0.4:
766 | version "5.2.1"
767 | resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.2.1.tgz#4dae1fea17270f457954b4a45234bba5fc796d16"
768 | integrity sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==
769 | dependencies:
770 | gaxios "^4.0.0"
771 | google-p12-pem "^3.0.3"
772 | jws "^4.0.0"
773 |
774 | hash-stream-validation@^0.2.2:
775 | version "0.2.4"
776 | resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz#ee68b41bf822f7f44db1142ec28ba9ee7ccb7512"
777 | integrity sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==
778 |
779 | http-errors@1.7.2:
780 | version "1.7.2"
781 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
782 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
783 | dependencies:
784 | depd "~1.1.2"
785 | inherits "2.0.3"
786 | setprototypeof "1.1.1"
787 | statuses ">= 1.5.0 < 2"
788 | toidentifier "1.0.0"
789 |
790 | http-errors@~1.7.2:
791 | version "1.7.3"
792 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
793 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
794 | dependencies:
795 | depd "~1.1.2"
796 | inherits "2.0.4"
797 | setprototypeof "1.1.1"
798 | statuses ">= 1.5.0 < 2"
799 | toidentifier "1.0.0"
800 |
801 | http-parser-js@>=0.5.1:
802 | version "0.5.3"
803 | resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9"
804 | integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==
805 |
806 | http-proxy-agent@^4.0.0:
807 | version "4.0.1"
808 | resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
809 | integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
810 | dependencies:
811 | "@tootallnate/once" "1"
812 | agent-base "6"
813 | debug "4"
814 |
815 | https-proxy-agent@^5.0.0:
816 | version "5.0.0"
817 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
818 | integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
819 | dependencies:
820 | agent-base "6"
821 | debug "4"
822 |
823 | iconv-lite@0.4.24:
824 | version "0.4.24"
825 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
826 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
827 | dependencies:
828 | safer-buffer ">= 2.1.2 < 3"
829 |
830 | imurmurhash@^0.1.4:
831 | version "0.1.4"
832 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
833 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
834 |
835 | inherits@2.0.3:
836 | version "2.0.3"
837 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
838 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
839 |
840 | inherits@2.0.4, inherits@^2.0.3:
841 | version "2.0.4"
842 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
843 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
844 |
845 | ipaddr.js@1.9.1:
846 | version "1.9.1"
847 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
848 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
849 |
850 | is-fullwidth-code-point@^3.0.0:
851 | version "3.0.0"
852 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
853 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
854 |
855 | is-obj@^2.0.0:
856 | version "2.0.0"
857 | resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
858 | integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
859 |
860 | is-stream-ended@^0.1.4:
861 | version "0.1.4"
862 | resolved "https://registry.yarnpkg.com/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda"
863 | integrity sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==
864 |
865 | is-stream@^2.0.0:
866 | version "2.0.0"
867 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
868 | integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
869 |
870 | is-typedarray@^1.0.0:
871 | version "1.0.0"
872 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
873 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
874 |
875 | jose@^2.0.5:
876 | version "2.0.5"
877 | resolved "https://registry.yarnpkg.com/jose/-/jose-2.0.5.tgz#29746a18d9fff7dcf9d5d2a6f62cb0c7cd27abd3"
878 | integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==
879 | dependencies:
880 | "@panva/asn1.js" "^1.0.0"
881 |
882 | json-bigint@^1.0.0:
883 | version "1.0.0"
884 | resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
885 | integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
886 | dependencies:
887 | bignumber.js "^9.0.0"
888 |
889 | jsonwebtoken@^8.5.1:
890 | version "8.5.1"
891 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
892 | integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
893 | dependencies:
894 | jws "^3.2.2"
895 | lodash.includes "^4.3.0"
896 | lodash.isboolean "^3.0.3"
897 | lodash.isinteger "^4.0.4"
898 | lodash.isnumber "^3.0.3"
899 | lodash.isplainobject "^4.0.6"
900 | lodash.isstring "^4.0.1"
901 | lodash.once "^4.0.0"
902 | ms "^2.1.1"
903 | semver "^5.6.0"
904 |
905 | jwa@^1.4.1:
906 | version "1.4.1"
907 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
908 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
909 | dependencies:
910 | buffer-equal-constant-time "1.0.1"
911 | ecdsa-sig-formatter "1.0.11"
912 | safe-buffer "^5.0.1"
913 |
914 | jwa@^2.0.0:
915 | version "2.0.0"
916 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
917 | integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
918 | dependencies:
919 | buffer-equal-constant-time "1.0.1"
920 | ecdsa-sig-formatter "1.0.11"
921 | safe-buffer "^5.0.1"
922 |
923 | jwks-rsa@^2.0.2:
924 | version "2.0.3"
925 | resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-2.0.3.tgz#4059f25e27f1d9cb5681dd12a98e46f8aa39fcbd"
926 | integrity sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg==
927 | dependencies:
928 | "@types/express-jwt" "0.0.42"
929 | debug "^4.1.0"
930 | jose "^2.0.5"
931 | limiter "^1.1.5"
932 | lru-memoizer "^2.1.2"
933 |
934 | jws@^3.2.2:
935 | version "3.2.2"
936 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
937 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
938 | dependencies:
939 | jwa "^1.4.1"
940 | safe-buffer "^5.0.1"
941 |
942 | jws@^4.0.0:
943 | version "4.0.0"
944 | resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
945 | integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
946 | dependencies:
947 | jwa "^2.0.0"
948 | safe-buffer "^5.0.1"
949 |
950 | limiter@^1.1.5:
951 | version "1.1.5"
952 | resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2"
953 | integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==
954 |
955 | lodash.camelcase@^4.3.0:
956 | version "4.3.0"
957 | resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
958 | integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
959 |
960 | lodash.clonedeep@^4.5.0:
961 | version "4.5.0"
962 | resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
963 | integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
964 |
965 | lodash.includes@^4.3.0:
966 | version "4.3.0"
967 | resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
968 | integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
969 |
970 | lodash.isboolean@^3.0.3:
971 | version "3.0.3"
972 | resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
973 | integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
974 |
975 | lodash.isinteger@^4.0.4:
976 | version "4.0.4"
977 | resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
978 | integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
979 |
980 | lodash.isnumber@^3.0.3:
981 | version "3.0.3"
982 | resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
983 | integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
984 |
985 | lodash.isplainobject@^4.0.6:
986 | version "4.0.6"
987 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
988 | integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
989 |
990 | lodash.isstring@^4.0.1:
991 | version "4.0.1"
992 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
993 | integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
994 |
995 | lodash.once@^4.0.0:
996 | version "4.1.1"
997 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
998 | integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
999 |
1000 | lodash@^4.17.14:
1001 | version "4.17.21"
1002 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
1003 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
1004 |
1005 | long@^4.0.0:
1006 | version "4.0.0"
1007 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
1008 | integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
1009 |
1010 | lru-cache@^6.0.0:
1011 | version "6.0.0"
1012 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
1013 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
1014 | dependencies:
1015 | yallist "^4.0.0"
1016 |
1017 | lru-cache@~4.0.0:
1018 | version "4.0.2"
1019 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
1020 | integrity sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=
1021 | dependencies:
1022 | pseudomap "^1.0.1"
1023 | yallist "^2.0.0"
1024 |
1025 | lru-memoizer@^2.1.2:
1026 | version "2.1.4"
1027 | resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.1.4.tgz#b864d92b557f00b1eeb322156a0409cb06dafac6"
1028 | integrity sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==
1029 | dependencies:
1030 | lodash.clonedeep "^4.5.0"
1031 | lru-cache "~4.0.0"
1032 |
1033 | make-dir@^3.0.0:
1034 | version "3.1.0"
1035 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
1036 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
1037 | dependencies:
1038 | semver "^6.0.0"
1039 |
1040 | media-typer@0.3.0:
1041 | version "0.3.0"
1042 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
1043 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
1044 |
1045 | merge-descriptors@1.0.1:
1046 | version "1.0.1"
1047 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
1048 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
1049 |
1050 | methods@~1.1.2:
1051 | version "1.1.2"
1052 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
1053 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
1054 |
1055 | mime-db@1.47.0, "mime-db@>= 1.43.0 < 2":
1056 | version "1.47.0"
1057 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
1058 | integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
1059 |
1060 | mime-types@^2.0.8, mime-types@~2.1.24:
1061 | version "2.1.30"
1062 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
1063 | integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
1064 | dependencies:
1065 | mime-db "1.47.0"
1066 |
1067 | mime@1.6.0:
1068 | version "1.6.0"
1069 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
1070 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
1071 |
1072 | mime@^2.2.0:
1073 | version "2.5.2"
1074 | resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
1075 | integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
1076 |
1077 | mimic-fn@^2.1.0:
1078 | version "2.1.0"
1079 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
1080 | integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
1081 |
1082 | ms@2.0.0:
1083 | version "2.0.0"
1084 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
1085 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
1086 |
1087 | ms@2.1.1:
1088 | version "2.1.1"
1089 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
1090 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
1091 |
1092 | ms@2.1.2:
1093 | version "2.1.2"
1094 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
1095 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
1096 |
1097 | ms@^2.1.1:
1098 | version "2.1.3"
1099 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
1100 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
1101 |
1102 | negotiator@0.6.2:
1103 | version "0.6.2"
1104 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
1105 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
1106 |
1107 | node-fetch@^2.3.0, node-fetch@^2.6.1:
1108 | version "2.6.1"
1109 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
1110 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
1111 |
1112 | node-forge@^0.10.0:
1113 | version "0.10.0"
1114 | resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
1115 | integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
1116 |
1117 | object-assign@^4:
1118 | version "4.1.1"
1119 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
1120 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
1121 |
1122 | object-hash@^2.1.1:
1123 | version "2.1.1"
1124 | resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09"
1125 | integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==
1126 |
1127 | on-finished@~2.3.0:
1128 | version "2.3.0"
1129 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
1130 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
1131 | dependencies:
1132 | ee-first "1.1.1"
1133 |
1134 | once@^1.3.1, once@^1.4.0:
1135 | version "1.4.0"
1136 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
1137 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
1138 | dependencies:
1139 | wrappy "1"
1140 |
1141 | onetime@^5.1.0:
1142 | version "5.1.2"
1143 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
1144 | integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
1145 | dependencies:
1146 | mimic-fn "^2.1.0"
1147 |
1148 | p-limit@^3.0.1:
1149 | version "3.1.0"
1150 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
1151 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
1152 | dependencies:
1153 | yocto-queue "^0.1.0"
1154 |
1155 | parseurl@~1.3.3:
1156 | version "1.3.3"
1157 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
1158 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
1159 |
1160 | path-to-regexp@0.1.7:
1161 | version "0.1.7"
1162 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
1163 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
1164 |
1165 | protobufjs@^6.10.0, protobufjs@^6.10.2, protobufjs@^6.8.6:
1166 | version "6.11.2"
1167 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
1168 | integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
1169 | dependencies:
1170 | "@protobufjs/aspromise" "^1.1.2"
1171 | "@protobufjs/base64" "^1.1.2"
1172 | "@protobufjs/codegen" "^2.0.4"
1173 | "@protobufjs/eventemitter" "^1.1.0"
1174 | "@protobufjs/fetch" "^1.1.0"
1175 | "@protobufjs/float" "^1.0.2"
1176 | "@protobufjs/inquire" "^1.1.0"
1177 | "@protobufjs/path" "^1.1.2"
1178 | "@protobufjs/pool" "^1.1.0"
1179 | "@protobufjs/utf8" "^1.1.0"
1180 | "@types/long" "^4.0.1"
1181 | "@types/node" ">=13.7.0"
1182 | long "^4.0.0"
1183 |
1184 | proxy-addr@~2.0.5:
1185 | version "2.0.6"
1186 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
1187 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
1188 | dependencies:
1189 | forwarded "~0.1.2"
1190 | ipaddr.js "1.9.1"
1191 |
1192 | pseudomap@^1.0.1:
1193 | version "1.0.2"
1194 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
1195 | integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
1196 |
1197 | pump@^3.0.0:
1198 | version "3.0.0"
1199 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
1200 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
1201 | dependencies:
1202 | end-of-stream "^1.1.0"
1203 | once "^1.3.1"
1204 |
1205 | pumpify@^2.0.0:
1206 | version "2.0.1"
1207 | resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-2.0.1.tgz#abfc7b5a621307c728b551decbbefb51f0e4aa1e"
1208 | integrity sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==
1209 | dependencies:
1210 | duplexify "^4.1.1"
1211 | inherits "^2.0.3"
1212 | pump "^3.0.0"
1213 |
1214 | qs@6.7.0:
1215 | version "6.7.0"
1216 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
1217 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
1218 |
1219 | range-parser@~1.2.1:
1220 | version "1.2.1"
1221 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
1222 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
1223 |
1224 | raw-body@2.4.0:
1225 | version "2.4.0"
1226 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
1227 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
1228 | dependencies:
1229 | bytes "3.1.0"
1230 | http-errors "1.7.2"
1231 | iconv-lite "0.4.24"
1232 | unpipe "1.0.0"
1233 |
1234 | readable-stream@^3.1.1:
1235 | version "3.6.0"
1236 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
1237 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
1238 | dependencies:
1239 | inherits "^2.0.3"
1240 | string_decoder "^1.1.1"
1241 | util-deprecate "^1.0.1"
1242 |
1243 | require-directory@^2.1.1:
1244 | version "2.1.1"
1245 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
1246 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
1247 |
1248 | retry-request@^4.0.0, retry-request@^4.1.1:
1249 | version "4.1.3"
1250 | resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.1.3.tgz#d5f74daf261372cff58d08b0a1979b4d7cab0fde"
1251 | integrity sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==
1252 | dependencies:
1253 | debug "^4.1.1"
1254 |
1255 | retry@0.12.0:
1256 | version "0.12.0"
1257 | resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
1258 | integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
1259 |
1260 | safe-buffer@5.1.2:
1261 | version "5.1.2"
1262 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
1263 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
1264 |
1265 | safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
1266 | version "5.2.1"
1267 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
1268 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
1269 |
1270 | "safer-buffer@>= 2.1.2 < 3":
1271 | version "2.1.2"
1272 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
1273 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
1274 |
1275 | semver@^5.6.0:
1276 | version "5.7.1"
1277 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
1278 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
1279 |
1280 | semver@^6.0.0:
1281 | version "6.3.0"
1282 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
1283 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
1284 |
1285 | send@0.17.1:
1286 | version "0.17.1"
1287 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
1288 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
1289 | dependencies:
1290 | debug "2.6.9"
1291 | depd "~1.1.2"
1292 | destroy "~1.0.4"
1293 | encodeurl "~1.0.2"
1294 | escape-html "~1.0.3"
1295 | etag "~1.8.1"
1296 | fresh "0.5.2"
1297 | http-errors "~1.7.2"
1298 | mime "1.6.0"
1299 | ms "2.1.1"
1300 | on-finished "~2.3.0"
1301 | range-parser "~1.2.1"
1302 | statuses "~1.5.0"
1303 |
1304 | serve-static@1.14.1:
1305 | version "1.14.1"
1306 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
1307 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
1308 | dependencies:
1309 | encodeurl "~1.0.2"
1310 | escape-html "~1.0.3"
1311 | parseurl "~1.3.3"
1312 | send "0.17.1"
1313 |
1314 | setprototypeof@1.1.1:
1315 | version "1.1.1"
1316 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
1317 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
1318 |
1319 | signal-exit@^3.0.2:
1320 | version "3.0.3"
1321 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
1322 | integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
1323 |
1324 | snakeize@^0.1.0:
1325 | version "0.1.0"
1326 | resolved "https://registry.yarnpkg.com/snakeize/-/snakeize-0.1.0.tgz#10c088d8b58eb076b3229bb5a04e232ce126422d"
1327 | integrity sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=
1328 |
1329 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
1330 | version "1.5.0"
1331 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
1332 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
1333 |
1334 | stream-events@^1.0.1, stream-events@^1.0.4, stream-events@^1.0.5:
1335 | version "1.0.5"
1336 | resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5"
1337 | integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==
1338 | dependencies:
1339 | stubs "^3.0.0"
1340 |
1341 | stream-shift@^1.0.0:
1342 | version "1.0.1"
1343 | resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
1344 | integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
1345 |
1346 | streamsearch@0.1.2:
1347 | version "0.1.2"
1348 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
1349 | integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
1350 |
1351 | string-width@^4.1.0, string-width@^4.2.0:
1352 | version "4.2.2"
1353 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
1354 | integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
1355 | dependencies:
1356 | emoji-regex "^8.0.0"
1357 | is-fullwidth-code-point "^3.0.0"
1358 | strip-ansi "^6.0.0"
1359 |
1360 | string_decoder@^1.1.1:
1361 | version "1.3.0"
1362 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
1363 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
1364 | dependencies:
1365 | safe-buffer "~5.2.0"
1366 |
1367 | strip-ansi@^6.0.0:
1368 | version "6.0.0"
1369 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
1370 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
1371 | dependencies:
1372 | ansi-regex "^5.0.0"
1373 |
1374 | stubs@^3.0.0:
1375 | version "3.0.0"
1376 | resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"
1377 | integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls=
1378 |
1379 | teeny-request@^7.0.0:
1380 | version "7.0.1"
1381 | resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.0.1.tgz#bdd41fdffea5f8fbc0d29392cb47bec4f66b2b4c"
1382 | integrity sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==
1383 | dependencies:
1384 | http-proxy-agent "^4.0.0"
1385 | https-proxy-agent "^5.0.0"
1386 | node-fetch "^2.6.1"
1387 | stream-events "^1.0.5"
1388 | uuid "^8.0.0"
1389 |
1390 | toidentifier@1.0.0:
1391 | version "1.0.0"
1392 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
1393 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
1394 |
1395 | tslib@^2.1.0:
1396 | version "2.2.0"
1397 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
1398 | integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
1399 |
1400 | type-is@~1.6.17, type-is@~1.6.18:
1401 | version "1.6.18"
1402 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
1403 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
1404 | dependencies:
1405 | media-typer "0.3.0"
1406 | mime-types "~2.1.24"
1407 |
1408 | typedarray-to-buffer@^3.1.5:
1409 | version "3.1.5"
1410 | resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
1411 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
1412 | dependencies:
1413 | is-typedarray "^1.0.0"
1414 |
1415 | unique-string@^2.0.0:
1416 | version "2.0.0"
1417 | resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
1418 | integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
1419 | dependencies:
1420 | crypto-random-string "^2.0.0"
1421 |
1422 | unpipe@1.0.0, unpipe@~1.0.0:
1423 | version "1.0.0"
1424 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
1425 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
1426 |
1427 | util-deprecate@^1.0.1:
1428 | version "1.0.2"
1429 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
1430 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
1431 |
1432 | utils-merge@1.0.1:
1433 | version "1.0.1"
1434 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
1435 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
1436 |
1437 | uuid@^8.0.0:
1438 | version "8.3.2"
1439 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
1440 | integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
1441 |
1442 | vary@^1, vary@~1.1.2:
1443 | version "1.1.2"
1444 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
1445 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
1446 |
1447 | websocket-driver@>=0.5.1:
1448 | version "0.7.4"
1449 | resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
1450 | integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
1451 | dependencies:
1452 | http-parser-js ">=0.5.1"
1453 | safe-buffer ">=5.1.0"
1454 | websocket-extensions ">=0.1.1"
1455 |
1456 | websocket-extensions@>=0.1.1:
1457 | version "0.1.4"
1458 | resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
1459 | integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
1460 |
1461 | wrap-ansi@^7.0.0:
1462 | version "7.0.0"
1463 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
1464 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
1465 | dependencies:
1466 | ansi-styles "^4.0.0"
1467 | string-width "^4.1.0"
1468 | strip-ansi "^6.0.0"
1469 |
1470 | wrappy@1:
1471 | version "1.0.2"
1472 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
1473 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
1474 |
1475 | write-file-atomic@^3.0.0:
1476 | version "3.0.3"
1477 | resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
1478 | integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
1479 | dependencies:
1480 | imurmurhash "^0.1.4"
1481 | is-typedarray "^1.0.0"
1482 | signal-exit "^3.0.2"
1483 | typedarray-to-buffer "^3.1.5"
1484 |
1485 | xdg-basedir@^4.0.0:
1486 | version "4.0.0"
1487 | resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
1488 | integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
1489 |
1490 | y18n@^5.0.5:
1491 | version "5.0.8"
1492 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
1493 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
1494 |
1495 | yallist@^2.0.0:
1496 | version "2.1.2"
1497 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
1498 | integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
1499 |
1500 | yallist@^4.0.0:
1501 | version "4.0.0"
1502 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
1503 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
1504 |
1505 | yargs-parser@^20.2.2:
1506 | version "20.2.7"
1507 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
1508 | integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
1509 |
1510 | yargs@^16.1.1:
1511 | version "16.2.0"
1512 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
1513 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
1514 | dependencies:
1515 | cliui "^7.0.2"
1516 | escalade "^3.1.1"
1517 | get-caller-file "^2.0.5"
1518 | require-directory "^2.1.1"
1519 | string-width "^4.2.0"
1520 | y18n "^5.0.5"
1521 | yargs-parser "^20.2.2"
1522 |
1523 | yocto-queue@^0.1.0:
1524 | version "0.1.0"
1525 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
1526 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
1527 |
--------------------------------------------------------------------------------
/test/lib/commonjs.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 | const backupfire = require('../../lib')
3 |
4 | assert(
5 | typeof backupfire === 'function',
6 | "CommonJS require does't work as expected"
7 | )
8 |
--------------------------------------------------------------------------------
/test/lib/ts.ts:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import backupfire from '../../lib'
3 |
4 | assert(typeof backupfire === 'function')
5 |
--------------------------------------------------------------------------------
/test/server/.env.example:
--------------------------------------------------------------------------------
1 | BACKUPFIRE_TOKEN=TODO
2 | BACKUPFIRE_PASSWORD=TODO
--------------------------------------------------------------------------------
/test/server/.firebaserc.example:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "backup-fire-playground"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/server/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "source": "."
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/server/index.js:
--------------------------------------------------------------------------------
1 | const backupfireAgent = require('@backupfire/firebase')
2 | const admin = require('firebase-admin')
3 |
4 | admin.initializeApp()
5 |
6 | exports.backupfire = backupfireAgent({
7 | controllerDomain: 'staging.backupfire.dev'
8 | })
9 |
--------------------------------------------------------------------------------
/test/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backupfire-firebase-test-server",
3 | "version": "1.0.0",
4 | "description": "Backup Fire test server",
5 | "main": "index.js",
6 | "author": "Sasha Koss ",
7 | "license": "MIT",
8 | "engines": {
9 | "node": "16"
10 | },
11 | "dependencies": {
12 | "@backupfire/firebase": "^1.8.0",
13 | "firebase-admin": "^10.2.0",
14 | "firebase-functions": "^3.21.2",
15 | "firebase-tools": "^11.0.1"
16 | },
17 | "scripts": {
18 | "seedUsers": "env GOOGLE_APPLICATION_CREDENTIALS=secrets/backup-fire-playground-alt.json node scripts/seedUsers.js"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/server/scripts/seedUsers.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 |
3 | admin.initializeApp()
4 |
5 | const number = parseInt(process.env.NUMBER)
6 | if (isNaN(number))
7 | throw new Error('The NUMBER environment variable must be a number')
8 |
9 | const auth = admin.auth()
10 |
11 | async function main() {
12 | for (let bunch = 0; bunch < number; bunch++) {
13 | console.log(`=== Creating users bunch #${bunch} ===`)
14 |
15 | await Promise.all(
16 | new Array(100).fill(undefined).map((_, i) => {
17 | const email = `test${bunch}${i}${Date.now()}@backupfire.dev`
18 |
19 | console.log(`...creating user #${bunch}/${i} (${email})`)
20 |
21 | return auth.createUser({
22 | email,
23 | password: Date.now().toString(),
24 | displayName: 'Sasha Clone',
25 | photoURL:
26 | 'https://pbs.twimg.com/profile_images/979030533719064576/rD33B86M_400x400.jpg',
27 | })
28 | })
29 | )
30 | }
31 | }
32 |
33 | main()
34 |
--------------------------------------------------------------------------------
/test/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["test/server/**/*.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": "src",
4 | "declaration": true,
5 | "target": "es2018",
6 | "module": "commonjs",
7 | "sourceMap": true,
8 | "outDir": "lib",
9 | "strict": true,
10 | "noImplicitAny": true,
11 | "esModuleInterop": true,
12 | "resolveJsonModule": true,
13 | "skipLibCheck": true
14 | },
15 | "exclude": [
16 | "**/test.ts",
17 | "lib",
18 | "node_modules",
19 | "test",
20 | "examples",
21 | "extension"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/types/firebase-tools.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'firebase-tools' {
2 | export const auth: {
3 | export: (path: string, options: { project: string }) => Promise
4 | }
5 | }
6 |
--------------------------------------------------------------------------------