From 664e3e452bba589545fb2dfabd0df07c32a1c3c0 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:12:58 +0000 Subject: [PATCH 01/12] 2nd gen auth sample --- Node/quickstarts/email-users/.gitignore | 65 + Node/quickstarts/email-users/README.md | 62 + Node/quickstarts/email-users/firebase.json | 25 + .../email-users/functions/eslint.config.js | 16 + .../email-users/functions/index.js | 107 + .../email-users/functions/package.json | 26 + .../email-users/functions/pnpm-lock.yaml | 2909 +++++++++++++++++ .../email-users/public/firebase-logo.png | Bin 0 -> 3542 bytes .../quickstarts/email-users/public/index.html | 79 + Node/quickstarts/email-users/public/main.css | 54 + Node/quickstarts/email-users/public/main.js | 76 + README.md | 3 +- 12 files changed, 3420 insertions(+), 2 deletions(-) create mode 100644 Node/quickstarts/email-users/.gitignore create mode 100644 Node/quickstarts/email-users/README.md create mode 100644 Node/quickstarts/email-users/firebase.json create mode 100644 Node/quickstarts/email-users/functions/eslint.config.js create mode 100644 Node/quickstarts/email-users/functions/index.js create mode 100644 Node/quickstarts/email-users/functions/package.json create mode 100644 Node/quickstarts/email-users/functions/pnpm-lock.yaml create mode 100644 Node/quickstarts/email-users/public/firebase-logo.png create mode 100644 Node/quickstarts/email-users/public/index.html create mode 100644 Node/quickstarts/email-users/public/main.css create mode 100644 Node/quickstarts/email-users/public/main.js diff --git a/Node/quickstarts/email-users/.gitignore b/Node/quickstarts/email-users/.gitignore new file mode 100644 index 0000000000..f62685251b --- /dev/null +++ b/Node/quickstarts/email-users/.gitignore @@ -0,0 +1,65 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env diff --git a/Node/quickstarts/email-users/README.md b/Node/quickstarts/email-users/README.md new file mode 100644 index 0000000000..0e3ed43fc4 --- /dev/null +++ b/Node/quickstarts/email-users/README.md @@ -0,0 +1,62 @@ +# Firebase SDK for Cloud Functions Quickstart - Auth triggers + +This quickstart demonstrates how to setup an Auth triggered Cloud Function using the **Firebase SDK for Cloud Functions**. + + +## Introduction + +We'll deploy 2nd gen Auth triggered Functions that send a welcome email when a new user signs up and a goodbye email when user accounts are deleted. + +Further reading: [Firebase SDK for Cloud Functions](https://firebase.google.com/docs/functions/) + + +## Functions Code + +See file [functions/index.js](functions/index.js) for the Functions trigger and the email sending code. + +Sending emails is performed using [nodemailer](https://www.npmjs.com/package/nodemailer), a node-based email client with comprehensive email server setup. For simplicity, in this sample we're showing how to send email through SMTP using a Gmail account. Be aware that Gmail has an [email sending quota](https://support.google.com/mail/answer/22839). If you are planning on sending a large number of emails you should use a professional email sending platform such as SendGrid, Mailjet, or Mailgun. + +> If switching to Sendgrid, Mailjet or Mailgun make sure you enable billing on your Firebase project as this is required to send requests to non-Google services. + +The dependencies are listed in [functions/package.json](functions/package.json). + +This sample comes with a simple web-based UI whose code is in the [public](public) directory that lets you easily sign in to Firebase and delete your account for purposes of testing the functions. + + +## Setting up the sample + + 1. Create a Firebase Project using the [Firebase Console](https://console.firebase.google.com). + 1. Enable the **Google** Provider in the **Auth** section. + 1. Clone or download this repo and open the `quickstarts/email-users` directory. + 1. You must have the Firebase CLI installed. If you don't have it install it with `npm install -g firebase-tools` and then configure it with `firebase login`. + 1. Configure the CLI locally by using `firebase use --add` and select your project in the list. + 1. Install Cloud Functions dependencies locally by running: `cd functions; npm install; cd -` + 1. To be able to send emails with your Gmail account: enable access to [Less Secure Apps](https://www.google.com/settings/security/lesssecureapps) and [Display Unlock Captcha](https://accounts.google.com/DisplayUnlockCaptcha). For accounts with 2-step verification enabled [Generate an App Password](https://support.google.com/accounts/answer/185833). + 1. This sample uses [parameterized configuration and Cloud KMS secrets](https://firebase.google.com/docs/functions/config-env). Set `GMAIL_EMAIL` and `GMAIL_PASSWORD`: + - For `GMAIL_PASSWORD`, store it as a secret using Cloud Secret Manager: + ```bash + firebase functions:secrets:set GMAIL_PASSWORD + ``` + - For `GMAIL_EMAIL`, you will be prompted when deploying, or you can define it in a `.env` file or `functions/.env`: + ```bash + GMAIL_EMAIL="myusername@gmail.com" + ``` + + +## Deploy and test + +This sample comes with a web-based UI for testing the function. To test it out: + + 1. Deploy your project using `firebase deploy` + 1. Open the app using `firebase open hosting:site`, this will open a browser. + 1. Sign in to the web app in the browser using Google Sign-In and delete your account using the button on the web app. You should receive email confirmations for each action. + + +## Contributing + +We'd love that you contribute to the project. Before doing so please read our [Contributor guide](../../CONTRIBUTING.md). + + +## License + +© Google, 2024. Licensed under an [Apache-2](../../LICENSE) license. diff --git a/Node/quickstarts/email-users/firebase.json b/Node/quickstarts/email-users/firebase.json new file mode 100644 index 0000000000..e43ee65531 --- /dev/null +++ b/Node/quickstarts/email-users/firebase.json @@ -0,0 +1,25 @@ +{ + "functions": { + "codebase": "email-users", + "predeploy": [ + "npm --prefix \"$RESOURCE_DIR\" run lint" + ] + }, + "hosting": { + "public": "./public" + }, + "emulators": { + "auth": { + "port": 9099 + }, + "functions": { + "port": 5001 + }, + "hosting": { + "port": 5000 + }, + "ui": { + "enabled": true + } + } +} diff --git a/Node/quickstarts/email-users/functions/eslint.config.js b/Node/quickstarts/email-users/functions/eslint.config.js new file mode 100644 index 0000000000..0afc889481 --- /dev/null +++ b/Node/quickstarts/email-users/functions/eslint.config.js @@ -0,0 +1,16 @@ +module.exports = [ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + rules: { + "no-console": "off", + "no-unused-vars": "off", + "no-undef": "off", + "no-empty": "off", + "no-useless-escape": "off", + "no-prototype-builtins": "off", + "no-redeclare": "off", + "no-constant-condition": "off", + "no-case-declarations": "off" + } + } +]; diff --git a/Node/quickstarts/email-users/functions/index.js b/Node/quickstarts/email-users/functions/index.js new file mode 100644 index 0000000000..8c15d02bb5 --- /dev/null +++ b/Node/quickstarts/email-users/functions/index.js @@ -0,0 +1,107 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +"use strict"; + +const {onUserCreated, onUserDeleted} = require("firebase-functions/identity"); +const {logger, onInit} = require("firebase-functions"); +const {defineString, defineSecret} = require("firebase-functions/params"); +const nodemailer = require("nodemailer"); + +// Configure the email transport using the default SMTP transport and a GMail account. +// For Gmail, enable these: +// 1. https://www.google.com/settings/security/lesssecureapps +// 2. https://accounts.google.com/DisplayUnlockCaptcha +// For other types of transports such as Sendgrid see https://nodemailer.com/transports/ +// TODO: Configure the `gmail.email` and `gmail.password` Google Cloud environment variables. +const gmailEmail = defineString("GMAIL_EMAIL"); +const gmailPassword = defineSecret("GMAIL_PASSWORD"); + +let mailTransport; +onInit(() => { + mailTransport = nodemailer.createTransport({ + service: "gmail", + auth: { + user: gmailEmail.value(), + pass: gmailPassword.value(), + }, + }); +}); + +// Your company name to include in the emails +// TODO: Change this to your app or company name to customize the email sent. +const APP_NAME = "Cloud Storage for Firebase quickstart"; + +// [START sendWelcomeEmail] +/** + * Sends a welcome email to new user. + */ +// [START onCreateTrigger] +exports.sendWelcomeEmail = onUserCreated({secrets: [gmailPassword]}, (event) => { +// [END onCreateTrigger] + // [START eventAttributes] + const user = event.data; + const email = user.email; // The email of the user. + const displayName = user.displayName; // The display name of the user. + // [END eventAttributes] + + return sendWelcomeEmail(email, displayName); +}); +// [END sendWelcomeEmail] + +// [START sendByeEmail] +/** + * Send an account deleted email confirmation to users who delete their accounts. + */ +// [START onDeleteTrigger] +exports.sendByeEmail = onUserDeleted({secrets: [gmailPassword]}, (event) => { +// [END onDeleteTrigger] + const user = event.data; + const email = user.email; + const displayName = user.displayName; + + return sendGoodbyeEmail(email, displayName); +}); +// [END sendByeEmail] + +// Sends a welcome email to the given user. +async function sendWelcomeEmail(email, displayName) { + const mailOptions = { + from: `${APP_NAME} `, + to: email, + }; + + // The user subscribed to the newsletter. + mailOptions.subject = `Welcome to ${APP_NAME}!`; + mailOptions.text = `Hey ${displayName || ""}! Welcome to ${APP_NAME}. I hope you will enjoy our service.`; + await mailTransport.sendMail(mailOptions); + logger.log("New welcome email sent to:", email); + return null; +} + +// Sends a goodbye email to the given user. +async function sendGoodbyeEmail(email, displayName) { + const mailOptions = { + from: `${APP_NAME} `, + to: email, + }; + + // The user unsubscribed to the newsletter. + mailOptions.subject = "Bye!"; + mailOptions.text = `Hey ${displayName || ""}!, We confirm that we have deleted your ${APP_NAME} account.`; + await mailTransport.sendMail(mailOptions); + logger.log("Account deletion confirmation email sent to:", email); + return null; +} diff --git a/Node/quickstarts/email-users/functions/package.json b/Node/quickstarts/email-users/functions/package.json new file mode 100644 index 0000000000..7979f7fff9 --- /dev/null +++ b/Node/quickstarts/email-users/functions/package.json @@ -0,0 +1,26 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": { + "lint": "eslint .", + "serve": "firebase emulators:start --only functions", + "shell": "firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log", + "compile": "cp ../../../../tsconfig.template.json ./tsconfig-compile.json && tsc --project tsconfig-compile.json" + }, + "engines": { + "node": "24" + }, + "dependencies": { + "firebase-admin": "^14.2.0", + "firebase-functions": "^7.3.2", + "nodemailer": "^8.0.11" + }, + "devDependencies": { + "eslint": "^8.57.1", + "eslint-plugin-promise": "^7.3.0" + }, + "private": true +} diff --git a/Node/quickstarts/email-users/functions/pnpm-lock.yaml b/Node/quickstarts/email-users/functions/pnpm-lock.yaml new file mode 100644 index 0000000000..7da12a0ae2 --- /dev/null +++ b/Node/quickstarts/email-users/functions/pnpm-lock.yaml @@ -0,0 +1,2909 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + firebase-admin: + specifier: ^14.2.0 + version: 14.2.0 + firebase-functions: + specifier: ^7.3.2 + version: 7.3.2(firebase-admin@14.2.0) + nodemailer: + specifier: ^8.0.11 + version: 8.0.11 + devDependencies: + eslint: + specifier: ^8.57.1 + version: 8.57.1 + eslint-plugin-promise: + specifier: ^7.3.0 + version: 7.3.0(eslint@8.57.1) + +packages: + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + + '@firebase/app-check-interop-types@0.3.4': + resolution: {integrity: sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==} + + '@firebase/app-types@0.9.5': + resolution: {integrity: sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==} + + '@firebase/auth-interop-types@0.2.5': + resolution: {integrity: sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==} + + '@firebase/component@0.7.3': + resolution: {integrity: sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==} + engines: {node: '>=20.0.0'} + + '@firebase/database-compat@2.1.4': + resolution: {integrity: sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==} + engines: {node: '>=20.0.0'} + + '@firebase/database-types@1.0.20': + resolution: {integrity: sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==} + + '@firebase/database@1.1.3': + resolution: {integrity: sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==} + engines: {node: '>=20.0.0'} + + '@firebase/logger@0.5.1': + resolution: {integrity: sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==} + engines: {node: '>=20.0.0'} + + '@firebase/util@1.15.1': + resolution: {integrity: sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==} + engines: {node: '>=20.0.0'} + + '@google-cloud/firestore@8.7.0': + resolution: {integrity: sha512-EvMpZQUXkTRdweSvOu6VL6EEQwHjHAgWz2UYZR+Mj6Ao52S+TWieHbSn15jiNnEw8F8RhbZj7IGXZ1PFB1eA+A==} + engines: {node: '>=18'} + + '@google-cloud/paginator@5.0.2': + resolution: {integrity: sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==} + engines: {node: '>=14.0.0'} + + '@google-cloud/projectify@4.0.0': + resolution: {integrity: sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==} + engines: {node: '>=14.0.0'} + + '@google-cloud/promisify@4.0.0': + resolution: {integrity: sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==} + engines: {node: '>=14'} + + '@google-cloud/storage@7.21.0': + resolution: {integrity: sha512-l+IFTkd+6Y5LoAuXyYCKNAKtw/Ci+rAMqgdTB1jv4iZiLhw0rtq+0qjIRbBizXkNzEFmXiXUW0H7sZQQvk1ffA==} + engines: {node: '>=14'} + + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@nodable/entities@3.0.0': + resolution: {integrity: sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@tootallnate/once@2.0.1': + resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} + engines: {node: '>= 10'} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/caseless@0.12.5': + resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/express-serve-static-core@5.1.2': + resolution: {integrity: sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@26.1.2': + resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/request@2.48.13': + resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@ungap/structured-clone@1.3.3': + resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anynum@1.0.1: + resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + arrify@2.0.1: + resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} + engines: {node: '>=8'} + + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + brace-expansion@1.1.17: + resolution: {integrity: sha512-w+aeW/mkgM4PyRMOJCgi3fOrTm5Q8QY1OSfn2TO2iuDj3ezIHqejmuxbjfPrqUkgqRew1iqkyAn0tr0ZwHD9+w==} + + brace-expansion@2.1.3: + resolution: {integrity: sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-promise@7.3.0: + resolution: {integrity: sha512-6uGiOR0INuujr6PEQmeSSP7GbIMJ/ebEXXiEzb/nOj68LknH5Pxzb/AbZivmr6VE6TkTE8rTjRK9zhKpK6HsRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-xml-builder@1.3.0: + resolution: {integrity: sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==} + + fast-xml-parser@5.10.1: + resolution: {integrity: sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==} + hasBin: true + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + firebase-admin@14.2.0: + resolution: {integrity: sha512-zfs5PdccEgjX479bbMmz95Rqc7ttQ4LV3qkGFlPiqOaOu/suBZc3fG52Qzn2hQjiSHkBM4NP2AZV5r2NvAt0TQ==} + engines: {node: '>=22'} + + firebase-functions@7.3.2: + resolution: {integrity: sha512-DTa5CtCNxFE1gPjkjlhl5uh8AcNWXLKORAMRenfkecP8zGstbje+r2TaNh4Ehdxp0qTPvTUe/sS23VESW32Gbg==} + engines: {node: '>=18.0.0'} + hasBin: true + peerDependencies: + '@apollo/server': ^5.2.0 + '@as-integrations/express4': ^1.1.2 + firebase-admin: ^11.10.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 + graphql: ^16.12.0 + peerDependenciesMeta: + '@apollo/server': + optional: true + '@as-integrations/express4': + optional: true + graphql: + optional: true + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.4.3: + resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@2.5.6: + resolution: {integrity: sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==} + engines: {node: '>= 0.12'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + + gaxios@6.7.1: + resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} + engines: {node: '>=14'} + + gaxios@7.1.3: + resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} + engines: {node: '>=18'} + + gaxios@7.3.0: + resolution: {integrity: sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==} + engines: {node: '>=18'} + + gcp-metadata@6.1.1: + resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} + engines: {node: '>=14'} + + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + + gcp-metadata@8.1.4: + resolution: {integrity: sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw==} + engines: {node: '>=18'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + google-auth-library@10.5.0: + resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} + engines: {node: '>=18'} + + google-auth-library@10.9.1: + resolution: {integrity: sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==} + engines: {node: '>=18'} + + google-auth-library@9.15.1: + resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} + engines: {node: '>=14'} + + google-gax@5.0.8: + resolution: {integrity: sha512-M4vpZcXQIC1gqIVGQ7eaU3jXQA6zecStyTXu514TYfThlgSurYJOxHZo9fzU6hAgwPWvuEynAVHWyaIk80VeEA==} + engines: {node: '>=18'} + + google-logging-utils@0.0.2: + resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} + engines: {node: '>=14'} + + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + gtoken@7.1.0: + resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} + engines: {node: '>=14.0.0'} + + gtoken@8.0.0: + resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-parser-js@0.5.10: + resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unsafe@2.0.0: + resolution: {integrity: sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jose@6.2.5: + resolution: {integrity: sha512-2E5L2yRp03FnwreJLJX8/r7mHiZICCf8kG7fAsTWkSQTDAcc46NIZoQLKy+EJ8sPoJlxyS4OQR5H70LjIZZlIQ==} + + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + hasBin: true + + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jwks-rsa@4.1.0: + resolution: {integrity: sha512-sbkByqyATKYJP5F4RXj03N5TUNC0QLTjCAZvwTzC4BwJZ8e0/cWxN8YROnyUth2g1/ONWi4eSFHeu6oYalrc3Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >= 23.0.0} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru-memoizer@3.0.0: + resolution: {integrity: sha512-m83w/cYXLdUIboKSPxzPAGfYnk+vqeDYXuoSrQRw1q+yVEd8IXhvMufN8Q5TIPe7e2jyX4SRNrDJI2Skw1yznQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nodemailer@8.0.11: + resolution: {integrity: sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==} + engines: {node: '>=6.0.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-expression-matcher@1.6.2: + resolution: {integrity: sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==} + engines: {node: '>=14.0.0'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + proto3-json-serializer@3.0.4: + resolution: {integrity: sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==} + engines: {node: '>=18'} + + protobufjs@7.6.5: + resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + retry-request@7.0.2: + resolution: {integrity: sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==} + engines: {node: '>=14'} + + retry-request@8.0.4: + resolution: {integrity: sha512-pI6/7eabUYkZxamkOq0g0uMxKLLGnjzhefY+vL8bVXag5rto4OU2YBTPytWLuHH7aEKD6fn7kJycQfid0Mwnkw==} + engines: {node: '>=18'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stream-events@1.0.5: + resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@2.4.1: + resolution: {integrity: sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==} + + stubs@3.0.0: + resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + teeny-request@10.1.4: + resolution: {integrity: sha512-R1Cg4Vu0UULeDfHL/kjABLaTW++9yD/B6n2g48y5dJ04hsEaxcfmAqbNDzNsbqAYJyIpZafjklLG9YxRu9uzOg==} + engines: {node: '>=18'} + + teeny-request@9.0.0: + resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} + engines: {node: '>=14'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + websocket-driver@0.7.5: + resolution: {integrity: sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==} + engines: {node: '>=0.8.0'} + + websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-naming@0.3.0: + resolution: {integrity: sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==} + engines: {node: '>=16.0.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@fastify/busboy@3.2.0': {} + + '@firebase/app-check-interop-types@0.3.4': {} + + '@firebase/app-types@0.9.5': + dependencies: + '@firebase/logger': 0.5.1 + + '@firebase/auth-interop-types@0.2.5': {} + + '@firebase/component@0.7.3': + dependencies: + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/database-compat@2.1.4': + dependencies: + '@firebase/component': 0.7.3 + '@firebase/database': 1.1.3 + '@firebase/database-types': 1.0.20 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/database-types@1.0.20': + dependencies: + '@firebase/app-types': 0.9.5 + '@firebase/util': 1.15.1 + + '@firebase/database@1.1.3': + dependencies: + '@firebase/app-check-interop-types': 0.3.4 + '@firebase/auth-interop-types': 0.2.5 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + faye-websocket: 0.11.4 + tslib: 2.8.1 + + '@firebase/logger@0.5.1': + dependencies: + tslib: 2.8.1 + + '@firebase/util@1.15.1': + dependencies: + tslib: 2.8.1 + + '@google-cloud/firestore@8.7.0': + dependencies: + '@opentelemetry/api': 1.9.1 + fast-deep-equal: 3.1.3 + functional-red-black-tree: 1.0.1 + google-gax: 5.0.8 + protobufjs: 7.6.5 + transitivePeerDependencies: + - supports-color + optional: true + + '@google-cloud/paginator@5.0.2': + dependencies: + arrify: 2.0.1 + extend: 3.0.2 + optional: true + + '@google-cloud/projectify@4.0.0': + optional: true + + '@google-cloud/promisify@4.0.0': + optional: true + + '@google-cloud/storage@7.21.0': + dependencies: + '@google-cloud/paginator': 5.0.2 + '@google-cloud/projectify': 4.0.0 + '@google-cloud/promisify': 4.0.0 + abort-controller: 3.0.0 + async-retry: 1.3.3 + duplexify: 4.1.3 + fast-xml-parser: 5.10.1 + gaxios: 6.7.1 + google-auth-library: 9.15.1 + html-entities: 2.6.0 + mime: 3.0.0 + p-limit: 3.1.0 + retry-request: 7.0.2 + teeny-request: 9.0.0 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + '@grpc/grpc-js@1.14.4': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + optional: true + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.5 + yargs: 17.7.3 + optional: true + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + optional: true + + '@js-sdsl/ordered-map@4.4.2': + optional: true + + '@nodable/entities@3.0.0': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@opentelemetry/api@1.9.1': + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@tootallnate/once@2.0.1': + optional: true + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 26.1.2 + + '@types/caseless@0.12.5': + optional: true + + '@types/connect@3.4.38': + dependencies: + '@types/node': 26.1.2 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 26.1.2 + + '@types/express-serve-static-core@5.1.2': + dependencies: + '@types/node': 26.1.2 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.2 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 26.1.2 + + '@types/ms@2.1.0': {} + + '@types/node@26.1.2': + dependencies: + undici-types: 8.3.0 + + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + + '@types/request@2.48.13': + dependencies: + '@types/caseless': 0.12.5 + '@types/node': 26.1.2 + '@types/tough-cookie': 4.0.5 + form-data: 2.5.6 + optional: true + + '@types/send@1.2.1': + dependencies: + '@types/node': 26.1.2 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 26.1.2 + + '@types/tough-cookie@4.0.5': + optional: true + + '@ungap/structured-clone@1.3.3': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + optional: true + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + agent-base@7.1.4: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: + optional: true + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: + optional: true + + anynum@1.0.1: + optional: true + + argparse@2.0.1: {} + + arrify@2.0.1: + optional: true + + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + optional: true + + asynckit@0.4.0: + optional: true + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bignumber.js@9.3.1: {} + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.17: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.3: + dependencies: + balanced-match: 1.0.2 + optional: true + + buffer-equal-constant-time@1.0.1: {} + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + optional: true + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + optional: true + + concat-map@0.0.1: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@4.0.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: + optional: true + + depd@2.0.0: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexify@4.1.3: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 + optional: true + + eastasianwidth@0.2.0: + optional: true + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: + optional: true + + emoji-regex@9.2.2: + optional: true + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + optional: true + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + optional: true + + escalade@3.2.0: + optional: true + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-promise@7.3.0(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + eslint: 8.57.1 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.3 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.3.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: + optional: true + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-xml-builder@1.3.0: + dependencies: + path-expression-matcher: 1.6.2 + xml-naming: 0.3.0 + optional: true + + fast-xml-parser@5.10.1: + dependencies: + '@nodable/entities': 3.0.0 + fast-xml-builder: 1.3.0 + is-unsafe: 2.0.0 + path-expression-matcher: 1.6.2 + strnum: 2.4.1 + xml-naming: 0.3.0 + optional: true + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + faye-websocket@0.11.4: + dependencies: + websocket-driver: 0.7.5 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + firebase-admin@14.2.0: + dependencies: + '@fastify/busboy': 3.2.0 + '@firebase/database-compat': 2.1.4 + '@firebase/database-types': 1.0.20 + fast-deep-equal: 3.1.3 + google-auth-library: 10.9.1 + jsonwebtoken: 9.0.3 + jwks-rsa: 4.1.0 + optionalDependencies: + '@google-cloud/firestore': 8.7.0 + '@google-cloud/storage': 7.21.0 + transitivePeerDependencies: + - encoding + - supports-color + + firebase-functions@7.3.2(firebase-admin@14.2.0): + dependencies: + '@types/cors': 2.8.19 + '@types/express': 5.0.6 + cors: 2.8.6 + express: 5.2.1 + firebase-admin: 14.2.0 + protobufjs: 7.6.5 + transitivePeerDependencies: + - supports-color + + flat-cache@3.2.0: + dependencies: + flatted: 3.4.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.4.3: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + optional: true + + form-data@2.5.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + safe-buffer: 5.2.1 + optional: true + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs.realpath@1.0.0: {} + + function-bind@1.1.2: {} + + functional-red-black-tree@1.0.1: + optional: true + + gaxios@6.7.1: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + is-stream: 2.0.1 + node-fetch: 2.7.0 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + gaxios@7.1.3: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + rimraf: 5.0.10 + transitivePeerDependencies: + - supports-color + optional: true + + gaxios@7.3.0: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + + gcp-metadata@6.1.1: + dependencies: + gaxios: 6.7.1 + google-logging-utils: 0.0.2 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.3.0 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.4: + dependencies: + gaxios: 7.1.3 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + optional: true + + get-caller-file@2.0.5: + optional: true + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + optional: true + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + google-auth-library@10.5.0: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.3.0 + gcp-metadata: 8.1.4 + google-logging-utils: 1.1.3 + gtoken: 8.0.0 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + optional: true + + google-auth-library@10.9.1: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.3.0 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + google-auth-library@9.15.1: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.7.1 + gcp-metadata: 6.1.1 + gtoken: 7.1.0 + jws: 4.0.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + google-gax@5.0.8: + dependencies: + '@grpc/grpc-js': 1.14.4 + '@grpc/proto-loader': 0.8.1 + duplexify: 4.1.3 + google-auth-library: 10.5.0 + google-logging-utils: 1.1.3 + node-fetch: 3.3.2 + object-hash: 3.0.0 + proto3-json-serializer: 3.0.4 + protobufjs: 7.6.5 + retry-request: 8.0.4 + rimraf: 5.0.10 + transitivePeerDependencies: + - supports-color + optional: true + + google-logging-utils@0.0.2: + optional: true + + google-logging-utils@1.1.3: {} + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + gtoken@7.1.0: + dependencies: + gaxios: 6.7.1 + jws: 4.0.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + gtoken@8.0.0: + dependencies: + gaxios: 7.3.0 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + optional: true + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + optional: true + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + html-entities@2.6.0: + optional: true + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-parser-js@0.5.10: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.1 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: + optional: true + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-path-inside@3.0.3: {} + + is-promise@4.0.0: {} + + is-stream@2.0.1: + optional: true + + is-unsafe@2.0.0: + optional: true + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + optional: true + + jose@6.2.5: {} + + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.8.5 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jwks-rsa@4.1.0: + dependencies: + '@types/jsonwebtoken': 9.0.10 + debug: 4.4.3 + jose: 6.2.5 + limiter: 1.1.5 + lru-cache: 11.5.2 + lru-memoizer: 3.0.0 + transitivePeerDependencies: + - supports-color + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + limiter@1.1.5: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: + optional: true + + lodash.clonedeep@4.5.0: {} + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + long@5.3.2: {} + + lru-cache@10.4.3: + optional: true + + lru-cache@11.5.2: {} + + lru-memoizer@3.0.0: + dependencies: + lodash.clonedeep: 4.5.0 + lru-cache: 11.5.2 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.52.0: + optional: true + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + optional: true + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@3.0.0: + optional: true + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.17 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.3 + optional: true + + minipass@7.1.3: + optional: true + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + negotiator@1.0.0: {} + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + optional: true + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + nodemailer@8.0.11: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: + optional: true + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: + optional: true + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-expression-matcher@1.6.2: + optional: true + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + optional: true + + path-to-regexp@8.4.2: {} + + prelude-ls@1.2.1: {} + + proto3-json-serializer@3.0.4: + dependencies: + protobufjs: 7.6.5 + optional: true + + protobufjs@7.6.5: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.1.2 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + queue-microtask@1.2.3: {} + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + optional: true + + require-directory@2.1.1: + optional: true + + resolve-from@4.0.0: {} + + retry-request@7.0.2: + dependencies: + '@types/request': 2.48.13 + extend: 3.0.2 + teeny-request: 9.0.0 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + retry-request@8.0.4: + dependencies: + extend: 3.0.2 + teeny-request: 10.1.4 + transitivePeerDependencies: + - supports-color + optional: true + + retry@0.13.1: + optional: true + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rimraf@5.0.10: + dependencies: + glob: 10.5.0 + optional: true + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + semver@7.8.5: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: + optional: true + + statuses@2.0.2: {} + + stream-events@1.0.5: + dependencies: + stubs: 3.0.0 + optional: true + + stream-shift@1.0.3: + optional: true + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + optional: true + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + optional: true + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + optional: true + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + optional: true + + strip-json-comments@3.1.1: {} + + strnum@2.4.1: + dependencies: + anynum: 1.0.1 + optional: true + + stubs@3.0.0: + optional: true + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + teeny-request@10.1.4: + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + stream-events: 1.0.5 + transitivePeerDependencies: + - supports-color + optional: true + + teeny-request@9.0.0: + dependencies: + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + stream-events: 1.0.5 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + text-table@0.2.0: {} + + toidentifier@1.0.1: {} + + tr46@0.0.3: + optional: true + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + + undici-types@8.3.0: {} + + unpipe@1.0.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: + optional: true + + uuid@9.0.1: + optional: true + + vary@1.1.2: {} + + web-streams-polyfill@3.3.3: {} + + webidl-conversions@3.0.1: + optional: true + + websocket-driver@0.7.5: + dependencies: + http-parser-js: 0.5.10 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + + websocket-extensions@0.1.4: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + optional: true + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + optional: true + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + optional: true + + wrappy@1.0.2: {} + + xml-naming@0.3.0: + optional: true + + y18n@5.0.8: + optional: true + + yargs-parser@21.1.1: + optional: true + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + optional: true + + yocto-queue@0.1.0: {} diff --git a/Node/quickstarts/email-users/public/firebase-logo.png b/Node/quickstarts/email-users/public/firebase-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c498b958bb02961ead9bb0f2637ac2f6248af02d GIT binary patch literal 3542 zcmZu!cRU+h_Ybj)Qq-)(s1YPKNkxg3*n1UELhKQ<_D(fMZKbMec2Kj_Xw@E7)U2j8 zN~`stMT}Zc`|^zUeShPAKKGt`&i$V8J?F2RY-+5>!o7WQcGf_(E}bg0m|$ z%-8=E4FITxAx^KpuE98Pn6Hmt5F!jI{3il&dOtJ6gu#D8g1wQ#wnnC4Ekd9xSP7~K zl@(TJ0)xS7fp|BBxwh`#?xz}3*dsXD9|41fhK52z6`+JbcbFU;4u{Fg!{p^ zA1n;#50it+!u~&}U{AOIl=nZp&R+g<`;*mQ$<$6;gU||e#RU@rEeQl4bHbfOj&E74B0J{|&*^Cpb1BAqGTE8tLyG7FqtZ)s zh>R;g0>;R%2{qAUbFaV6(aVkpD}>&U9wTS$eIwK~&o0oHd80@0*=|KbFv-|QwTukv z#TYpdMd#o%`6eA&{`h3=ry__P!P6;XejO_hl2EWwIT*&d#w|pIlgbQ!E4!R?e)5;o zVWIrC!$8MzoJSi1+}n`by$i6rR^d}TWQ-gW4-LI@Q}W}dE9M}!^lHWFh6e4i7%yd` zTz3PbewpB=hml%Du0TCf1) zGwt&;Vyf;*|5z5+*m74Jj?csHvkM;fmB@vuuGw}w_eSkL>!9Q`QrF~M&M|%X{@6){ z6~9t{jV5tNYUmvoCnFqn&X#eju4Y{4M)F_?aoddk=zPn(Zriq2ZsZR(13P;G0qfA3 zJ&647mx(ESEv?}v4GHC7{ykLD_jmSl+WZWe=aPFjw4!$RuVAHg^5j}iM4Jscls<{O zitcRL%HA)DB1P4E>O|j|Ib5^+_;UGK0GQup2KzjHQr2lA;x#7nd>3lU+@rK-W%D(| z$?@n1iQ(uwZf1mw=gT(K)fJl@^Xc51X8tvu+!4eCVW-9_<@QY^f@()0dT@7`zRmr z6=k6suw>JJ=Y-o6!P!Y0%6wBcwQ&?V)X9I8HcXVzoNJ6|ijDE(=Ypo2TGS>k0Hc*# zbk=jjH9G3Dl<4rO*FGPZJTH{fzyCgrx!WwBo#zRd%uxNh^6|_Pz5yw51dv*zzlrS- zZszP7o#rq%@zK#NiumZqH24cWdT0#szg>vum0!=qvEq~D$6gI3@;#OVzZPI)8k4!v z+6m%7Nj)dnJko-1%figO{!Y;#dH)(M&2wA>d|$A<+m4qY?Q3dmjfnr z;8Kux#ButkxZ}f_WA4XuELAox8cllm?hOcY=bQ{7ovQgloE!jGid?Ed`$fT;2HYkp zNZ~Vgngs>vbH_MLGGcRnHSj}}x376wu`RC@U1J6GZFx}!^Tm6W1AUo?T20e9DfTo1 zHB8!3F(wLCFEJ|?^^Dt5TW+2-Vz-##ri5ix5A&u=CwOh7y8go8ZGAbpC#wVC- zX(jS@A0@?05mm6FBMMCy!0%PEI-gAySjNMNay*nvUhcYx%LV7RfY(r1h$qZis$mfE zSUnTCf&`Fc+1|7{;UAPPP2}EHUDDDwp0EGhYoN#NF5v+ry)-UhRI?%We*iQpYl_+5 z#3Wl;avCznB&Kbs&7uJC4TR>C>EyBFX_x-MwGpj!v=5NN2ux8(DqG7P&F+lKESv>u zSH-znrtxNrgO|NM-&Se~7115+IxQth+}3<;)p(TF$CBFi!x~#?J;vLPW{$=8D?g~n z)+g$f(aZN^z35)H5=5F87Ib3xz$lL#8!GBO(Y8B!c10xORea2G`Hy&8ol z%Y3VtoBTBPU<$yH6gJ+YY(kd~AYk~OP%o*_$`ZiyH?1|+3LK$iuFx%u`Tl;Sni z&|KnQsCwF2rwo|y37dsBuzb^TYtUpfS8_Ae$Jl{HXyUxOgI+E3L_$}sJ1is5@%?ym z@0|yb6NA?oP@PIpNzli|G5~L^M@hYb{ zYGnCk*=3iYsI`314M#ow#=ZSeP7dK*HY&%I_+4!I#YiZ;^HH%EkSwU8h`XAgb1S(_ z;O#Bx-F4kKHZv=SM>$U}s|)33AZJ$(LICikxOWT-z;5B|yvhX^cJz3qu3L%q9S)7O zkA#>v3v?PoCyl?MjOB!$CnIerFNd3zMd;_g%y4ATCZ-402g&x3rSC3iy1yG$y7;^e zZfV)Tu(HDRSRl+s^F=w@z36KhN4XNx#tK~2{mOZ{#i)tZUDWxbkGRr?Y)$XHU;lva zYBWx7hJ2r1n}V*zUWjFVxiOtYa-34p0iZ^XE`87Il&6L2Rtikyb$o2<6 z74>>wlp#rWZ2qsH1gK%X21*2qx5$Ss{(rdh&A6J4phlV3Gp-`3(7 zdqZVVMKQz&9Q$Z$S#-jmUD-x3#h{ZKrQ0beRuRoCEJhcf90om)LwY_ER;sG3N@E6% zs5<8p=JC&DMC!O6CQ<3h`mBpxWmj(-W`h{1S%rCM=zJw?>cjn zZ;3R31tuT%x)_Ufw8>X9(a^iRk8XYPhkO4H*`-}WW`Bx@Sqn3gy5wqFbNSvF7i5^i zig6O(qa})pD>Ai)x`qoyM^1mKTnSk6S@Q5}JP1Z0e$p9Uw(}-hV*KOGwfzL0>F8vi!;@n(Rn4TI%;#8zSE~ zeDVE{u+bK$yW`N$<>NmaOq;ZhpJXY~ho2kM!`-h+RT^P_N4iP=3;g1q)j8Q}tpn>y3g+-)KpC<(#`!AY zS9@n&q|Q!DM^hVjA`PxfWb+8DBS&hN?|LwPRqxsiwUc4On-SKm+nyzZ8F x>>&cc4lOJCrO2L>Lst%ygz|w7B)Q?^gR3$(wijKRxl+zPTNoW + + + + + + + + Welcome email demo + + + + + + + + + +
+ + +
+
+
+

Welcome email demo

+
+
+
+
+
+ + +
+
+

+ This web application demonstrates how you can send a welcome email for new accounts. After signing-in for the first time you will receive a welcome email. + Now sign in! +

+ +
+
+ + +
+
+

+ Welcome
+ Your Firebase User ID is:
+ Your profile picture: +

+ + +
+
+
+
+
+ + + + + + + + + + + diff --git a/Node/quickstarts/email-users/public/main.css b/Node/quickstarts/email-users/public/main.css new file mode 100644 index 0000000000..b9d21d0491 --- /dev/null +++ b/Node/quickstarts/email-users/public/main.css @@ -0,0 +1,54 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +html, body { + font-family: 'Roboto', 'Helvetica', sans-serif; +} +.mdl-grid { + max-width: 1024px; + margin: auto; +} +.mdl-card { + min-height: 0; + padding-bottom: 5px; +} +.mdl-layout__header-row { + padding: 0; +} +h3 { + background: url('firebase-logo.png') no-repeat; + background-size: 40px; + padding-left: 50px; +} +#demo-signed-out-card, +#demo-signed-in-card { + display: none; +} +#demo-profile-pic { + height: 60px; + width: 60px; + border-radius: 30px; + margin-left: calc(50% - 30px); + margin-top: 10px; +} +#demo-name-container, +#demo-email-container, +#demo-uid-container { + font-weight: bold; +} +#demo-delete-button { + margin-left: 20px; +} diff --git a/Node/quickstarts/email-users/public/main.js b/Node/quickstarts/email-users/public/main.js new file mode 100644 index 0000000000..71dcde9626 --- /dev/null +++ b/Node/quickstarts/email-users/public/main.js @@ -0,0 +1,76 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +// Initializes the Demo. +function Demo() { + document.addEventListener('DOMContentLoaded', function() { + // Shortcuts to DOM Elements. + this.signInButton = document.getElementById('demo-sign-in-button'); + this.signOutButton = document.getElementById('demo-sign-out-button'); + this.nameContainer = document.getElementById('demo-name-container'); + this.uidContainer = document.getElementById('demo-uid-container'); + this.deleteButton = document.getElementById('demo-delete-button'); + this.profilePic = document.getElementById('demo-profile-pic'); + this.signedOutCard = document.getElementById('demo-signed-out-card'); + this.signedInCard = document.getElementById('demo-signed-in-card'); + + // Bind events. + this.signInButton.addEventListener('click', this.signIn.bind(this)); + this.signOutButton.addEventListener('click', this.signOut.bind(this)); + this.deleteButton.addEventListener('click', this.deleteAccount.bind(this)); + firebase.auth().onAuthStateChanged(this.onAuthStateChanged.bind(this)); + }.bind(this)); +} + +// Triggered on Firebase auth state change. +Demo.prototype.onAuthStateChanged = function(user) { + if (user) { + this.nameContainer.innerText = user.displayName; + this.uidContainer.innerText = user.uid; + this.profilePic.src = user.photoURL; + this.signedOutCard.style.display = 'none'; + this.signedInCard.style.display = 'block'; + } else { + this.signedOutCard.style.display = 'block'; + this.signedInCard.style.display = 'none'; + } +}; + +// Initiates the sign-in flow using LinkedIn sign in in a popup. +Demo.prototype.signIn = function() { + firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider()); +}; + +// Signs-out of Firebase. +Demo.prototype.signOut = function() { + firebase.auth().signOut(); +}; + +// Deletes the user's account. +Demo.prototype.deleteAccount = function() { + firebase.auth().currentUser.delete().then(function() { + window.alert('Account deleted. Check your email for a confirmation.'); + }).catch(function(error) { + if (error.code === 'auth/requires-recent-login') { + window.alert('You need to have recently signed-in to delete your account. Please sign-in and try again.'); + firebase.auth().signOut(); + } + }); +}; + +// Load the demo. +window.demo = new Demo(); diff --git a/README.md b/README.md index 9ecbdf405d..d53679bc4b 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,7 @@ This quickstart sample demonstrates using **Cloud Functions** triggered by **Fir ### Auth trigger quickstart: Welcome Email -> Auth user create and delete triggers aren't yet supported by 2nd gen functions - +- [Node 2nd gen](/Node/quickstarts/email-users/) - [Node 1st gen](/Node-1st-gen/quickstarts/email-users/) This quickstart sample demonstrates using **Cloud Functions** triggered by **Firebase Auth events**. The function sends a Welcome Email when user accounts are created (or when users sign-in using an Identity Provider for the first time) and sends a Goodbye Email when user accounts are deleted. From 06d960e4258c8548af9f1fe96b7c057fefccc159 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:23:28 +0000 Subject: [PATCH 02/12] break into separate files for better snippets --- Node/quickstarts/email-users/README.md | 89 +++++++++------ Node/quickstarts/email-users/firebase.json | 6 - .../email-users/functions/eslint.config.js | 16 +++ .../email-users/functions/index.js | 103 ++---------------- .../email-users/functions/package.json | 4 +- .../email-users/functions/sendByeEmail.js | 32 ++++++ .../email-users/functions/sendWelcomeEmail.js | 34 ++++++ .../email-users/functions/tenants.js | 48 ++++++++ .../functions/utils/myEmailService.js | 84 ++++++++++++++ .../email-users/public/firebase-logo.png | Bin 3542 -> 0 bytes .../quickstarts/email-users/public/index.html | 79 -------------- Node/quickstarts/email-users/public/main.css | 54 --------- Node/quickstarts/email-users/public/main.js | 76 ------------- 13 files changed, 286 insertions(+), 339 deletions(-) create mode 100644 Node/quickstarts/email-users/functions/sendByeEmail.js create mode 100644 Node/quickstarts/email-users/functions/sendWelcomeEmail.js create mode 100644 Node/quickstarts/email-users/functions/tenants.js create mode 100644 Node/quickstarts/email-users/functions/utils/myEmailService.js delete mode 100644 Node/quickstarts/email-users/public/firebase-logo.png delete mode 100644 Node/quickstarts/email-users/public/index.html delete mode 100644 Node/quickstarts/email-users/public/main.css delete mode 100644 Node/quickstarts/email-users/public/main.js diff --git a/Node/quickstarts/email-users/README.md b/Node/quickstarts/email-users/README.md index 0e3ed43fc4..76b306cb12 100644 --- a/Node/quickstarts/email-users/README.md +++ b/Node/quickstarts/email-users/README.md @@ -1,55 +1,82 @@ # Firebase SDK for Cloud Functions Quickstart - Auth triggers -This quickstart demonstrates how to setup an Auth triggered Cloud Function using the **Firebase SDK for Cloud Functions**. +This quickstart demonstrates how to setup an Auth triggered Cloud Function using the **Firebase SDK for Cloud Functions** and [Resend](https://resend.com/). ## Introduction -We'll deploy 2nd gen Auth triggered Functions that send a welcome email when a new user signs up and a goodbye email when user accounts are deleted. +We'll deploy 2nd gen Auth triggered functions that send a welcome email when a new user signs up and a goodbye email when user accounts are deleted. -Further reading: [Firebase SDK for Cloud Functions](https://firebase.google.com/docs/functions/) +- [Read more about Cloud Functions for Firebase](https://firebase.google.com/docs/functions/) +- [Read more about the Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) +- [Connect Cloud Functions to the Emulator](https://firebase.google.com/docs/emulator-suite/connect_functions) ## Functions Code -See file [functions/index.js](functions/index.js) for the Functions trigger and the email sending code. +The functions are organized into separate files for clean, self-contained documentation snippets and modular code: -Sending emails is performed using [nodemailer](https://www.npmjs.com/package/nodemailer), a node-based email client with comprehensive email server setup. For simplicity, in this sample we're showing how to send email through SMTP using a Gmail account. Be aware that Gmail has an [email sending quota](https://support.google.com/mail/answer/22839). If you are planning on sending a large number of emails you should use a professional email sending platform such as SendGrid, Mailjet, or Mailgun. +- [functions/sendWelcomeEmail.js](functions/sendWelcomeEmail.js): `sendWelcomeEmail` triggers on all user creations across the project (default behavior). +- [functions/sendByeEmail.js](functions/sendByeEmail.js): `sendByeEmail` triggers when any user account is deleted. +- [functions/tenants.js](functions/tenants.js): Multi-tenancy examples: + - `sendWelcomeEmailToTenant`: Scoped to users in a specific Identity Platform tenant using `tenantId: "my-tenant-id"`. + - `sendWelcomeEmailNoTenant`: Triggers only for users not associated with any tenant using `tenantId: IS_NOT_TENANT`. +- [functions/utils/myEmailService.js](functions/utils/myEmailService.js): Configures the Resend client, defines the `EMAIL_API_KEY` secret, and provides the `sendEmail`, `sendWelcomeEmail`, and `sendGoodbyeEmail` helpers. +- [functions/index.js](functions/index.js): Entry point re-exporting all function triggers. -> If switching to Sendgrid, Mailjet or Mailgun make sure you enable billing on your Firebase project as this is required to send requests to non-Google services. +Sending emails is performed using [Resend](https://resend.com/). The dependencies are listed in [functions/package.json](functions/package.json). -The dependencies are listed in [functions/package.json](functions/package.json). -This sample comes with a simple web-based UI whose code is in the [public](public) directory that lets you easily sign in to Firebase and delete your account for purposes of testing the functions. +## Set up the sample +1. Clone or download this repo and navigate to `Node/quickstarts/email-users`: + ```bash + cd Node/quickstarts/email-users + ``` +2. Install Cloud Functions dependencies: + ```bash + cd functions && pnpm install && cd .. + ``` +3. Set your Resend API key for local development in `functions/.env.local`: + ```bash + EMAIL_API_KEY="re_123456789" + ``` + > You can obtain a free API key from [Resend](https://resend.com/api-keys). -## Setting up the sample - 1. Create a Firebase Project using the [Firebase Console](https://console.firebase.google.com). - 1. Enable the **Google** Provider in the **Auth** section. - 1. Clone or download this repo and open the `quickstarts/email-users` directory. - 1. You must have the Firebase CLI installed. If you don't have it install it with `npm install -g firebase-tools` and then configure it with `firebase login`. - 1. Configure the CLI locally by using `firebase use --add` and select your project in the list. - 1. Install Cloud Functions dependencies locally by running: `cd functions; npm install; cd -` - 1. To be able to send emails with your Gmail account: enable access to [Less Secure Apps](https://www.google.com/settings/security/lesssecureapps) and [Display Unlock Captcha](https://accounts.google.com/DisplayUnlockCaptcha). For accounts with 2-step verification enabled [Generate an App Password](https://support.google.com/accounts/answer/185833). - 1. This sample uses [parameterized configuration and Cloud KMS secrets](https://firebase.google.com/docs/functions/config-env). Set `GMAIL_EMAIL` and `GMAIL_PASSWORD`: - - For `GMAIL_PASSWORD`, store it as a secret using Cloud Secret Manager: - ```bash - firebase functions:secrets:set GMAIL_PASSWORD - ``` - - For `GMAIL_EMAIL`, you will be prompted when deploying, or you can define it in a `.env` file or `functions/.env`: - ```bash - GMAIL_EMAIL="myusername@gmail.com" - ``` +## Run locally with the Firebase Emulator Suite +The [Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) lets you test Auth triggers and create/delete users directly in the Emulator UI without deploying to a live project. -## Deploy and test +1. Start the emulators: + ```bash + firebase emulators:start + ``` +2. Open the **Emulator Suite UI** in your browser at [http://localhost:4000](http://localhost:4000) (or the port printed in your terminal). +3. Navigate to the **Authentication** tab. +4. **Trigger `sendWelcomeEmail`**: Click **Add user**, enter an email address and display name, and click **Save**. +5. **Trigger `sendByeEmail`**: Select the user you just created and click **Delete user**. +6. View the logs in the **Logs** tab of the Emulator Suite UI or in your terminal to see the functions execute: + > `New welcome email sent to: user@example.com` + > `Account deletion confirmation email sent to: user@example.com` -This sample comes with a web-based UI for testing the function. To test it out: - 1. Deploy your project using `firebase deploy` - 1. Open the app using `firebase open hosting:site`, this will open a browser. - 1. Sign in to the web app in the browser using Google Sign-In and delete your account using the button on the web app. You should receive email confirmations for each action. +## Deploy to production + +To deploy the functions to a live Firebase project: + +1. Configure your Firebase project: + ```bash + firebase use --add + ``` +2. Set your Resend API key as a Cloud Secret: + ```bash + firebase functions:secrets:set EMAIL_API_KEY + ``` +3. Deploy the functions: + ```bash + firebase deploy --only functions + ``` ## Contributing @@ -59,4 +86,4 @@ We'd love that you contribute to the project. Before doing so please read our [C ## License -© Google, 2024. Licensed under an [Apache-2](../../LICENSE) license. +© Google, 2026. Licensed under an [Apache-2](../../LICENSE) license. diff --git a/Node/quickstarts/email-users/firebase.json b/Node/quickstarts/email-users/firebase.json index e43ee65531..51af2f6845 100644 --- a/Node/quickstarts/email-users/firebase.json +++ b/Node/quickstarts/email-users/firebase.json @@ -5,9 +5,6 @@ "npm --prefix \"$RESOURCE_DIR\" run lint" ] }, - "hosting": { - "public": "./public" - }, "emulators": { "auth": { "port": 9099 @@ -15,9 +12,6 @@ "functions": { "port": 5001 }, - "hosting": { - "port": 5000 - }, "ui": { "enabled": true } diff --git a/Node/quickstarts/email-users/functions/eslint.config.js b/Node/quickstarts/email-users/functions/eslint.config.js index 0afc889481..80492c6b15 100644 --- a/Node/quickstarts/email-users/functions/eslint.config.js +++ b/Node/quickstarts/email-users/functions/eslint.config.js @@ -1,3 +1,19 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + module.exports = [ { files: ["**/*.js", "**/*.cjs", "**/*.mjs"], diff --git a/Node/quickstarts/email-users/functions/index.js b/Node/quickstarts/email-users/functions/index.js index 8c15d02bb5..5d69d4358d 100644 --- a/Node/quickstarts/email-users/functions/index.js +++ b/Node/quickstarts/email-users/functions/index.js @@ -1,5 +1,5 @@ /** - * Copyright 2024 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,93 +15,14 @@ */ "use strict"; -const {onUserCreated, onUserDeleted} = require("firebase-functions/identity"); -const {logger, onInit} = require("firebase-functions"); -const {defineString, defineSecret} = require("firebase-functions/params"); -const nodemailer = require("nodemailer"); - -// Configure the email transport using the default SMTP transport and a GMail account. -// For Gmail, enable these: -// 1. https://www.google.com/settings/security/lesssecureapps -// 2. https://accounts.google.com/DisplayUnlockCaptcha -// For other types of transports such as Sendgrid see https://nodemailer.com/transports/ -// TODO: Configure the `gmail.email` and `gmail.password` Google Cloud environment variables. -const gmailEmail = defineString("GMAIL_EMAIL"); -const gmailPassword = defineSecret("GMAIL_PASSWORD"); - -let mailTransport; -onInit(() => { - mailTransport = nodemailer.createTransport({ - service: "gmail", - auth: { - user: gmailEmail.value(), - pass: gmailPassword.value(), - }, - }); -}); - -// Your company name to include in the emails -// TODO: Change this to your app or company name to customize the email sent. -const APP_NAME = "Cloud Storage for Firebase quickstart"; - -// [START sendWelcomeEmail] -/** - * Sends a welcome email to new user. - */ -// [START onCreateTrigger] -exports.sendWelcomeEmail = onUserCreated({secrets: [gmailPassword]}, (event) => { -// [END onCreateTrigger] - // [START eventAttributes] - const user = event.data; - const email = user.email; // The email of the user. - const displayName = user.displayName; // The display name of the user. - // [END eventAttributes] - - return sendWelcomeEmail(email, displayName); -}); -// [END sendWelcomeEmail] - -// [START sendByeEmail] -/** - * Send an account deleted email confirmation to users who delete their accounts. - */ -// [START onDeleteTrigger] -exports.sendByeEmail = onUserDeleted({secrets: [gmailPassword]}, (event) => { -// [END onDeleteTrigger] - const user = event.data; - const email = user.email; - const displayName = user.displayName; - - return sendGoodbyeEmail(email, displayName); -}); -// [END sendByeEmail] - -// Sends a welcome email to the given user. -async function sendWelcomeEmail(email, displayName) { - const mailOptions = { - from: `${APP_NAME} `, - to: email, - }; - - // The user subscribed to the newsletter. - mailOptions.subject = `Welcome to ${APP_NAME}!`; - mailOptions.text = `Hey ${displayName || ""}! Welcome to ${APP_NAME}. I hope you will enjoy our service.`; - await mailTransport.sendMail(mailOptions); - logger.log("New welcome email sent to:", email); - return null; -} - -// Sends a goodbye email to the given user. -async function sendGoodbyeEmail(email, displayName) { - const mailOptions = { - from: `${APP_NAME} `, - to: email, - }; - - // The user unsubscribed to the newsletter. - mailOptions.subject = "Bye!"; - mailOptions.text = `Hey ${displayName || ""}!, We confirm that we have deleted your ${APP_NAME} account.`; - await mailTransport.sendMail(mailOptions); - logger.log("Account deletion confirmation email sent to:", email); - return null; -} +const {sendWelcomeEmail} = require("./sendWelcomeEmail"); +const {sendByeEmail} = require("./sendByeEmail"); +const { + sendWelcomeEmailToTenant, + sendWelcomeEmailNoTenant, +} = require("./tenants"); + +exports.sendWelcomeEmail = sendWelcomeEmail; +exports.sendByeEmail = sendByeEmail; +exports.sendWelcomeEmailToTenant = sendWelcomeEmailToTenant; +exports.sendWelcomeEmailNoTenant = sendWelcomeEmailNoTenant; diff --git a/Node/quickstarts/email-users/functions/package.json b/Node/quickstarts/email-users/functions/package.json index 7979f7fff9..30b4aaae42 100644 --- a/Node/quickstarts/email-users/functions/package.json +++ b/Node/quickstarts/email-users/functions/package.json @@ -16,11 +16,11 @@ "dependencies": { "firebase-admin": "^14.2.0", "firebase-functions": "^7.3.2", - "nodemailer": "^8.0.11" + "resend": "^4.0.0" }, "devDependencies": { "eslint": "^8.57.1", - "eslint-plugin-promise": "^7.3.0" + "eslint-config-google": "^0.14.0" }, "private": true } diff --git a/Node/quickstarts/email-users/functions/sendByeEmail.js b/Node/quickstarts/email-users/functions/sendByeEmail.js new file mode 100644 index 0000000000..8377144b18 --- /dev/null +++ b/Node/quickstarts/email-users/functions/sendByeEmail.js @@ -0,0 +1,32 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +"use strict"; + +// [START sendByeEmail] +const {onUserDeleted} = require("firebase-functions/identity"); +const {defineSecret} = require("firebase-functions/params"); +const {sendGoodbyeEmail} = require("./utils/myEmailService"); + +const emailApiKey = defineSecret("EMAIL_API_KEY"); + +// [START onDeleteTrigger] +exports.sendByeEmail = onUserDeleted({secrets: [emailApiKey]}, async (event) => { +// [END onDeleteTrigger] + const {email, displayName} = event.data; + + await sendGoodbyeEmail(email, displayName); +}); +// [END sendByeEmail] diff --git a/Node/quickstarts/email-users/functions/sendWelcomeEmail.js b/Node/quickstarts/email-users/functions/sendWelcomeEmail.js new file mode 100644 index 0000000000..52c6cbe013 --- /dev/null +++ b/Node/quickstarts/email-users/functions/sendWelcomeEmail.js @@ -0,0 +1,34 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +"use strict"; + +// [START sendWelcomeEmail] +const {onUserCreated} = require("firebase-functions/identity"); +const {defineSecret} = require("firebase-functions/params"); +const {sendWelcomeEmail} = require("./utils/myEmailService"); + +const emailApiKey = defineSecret("EMAIL_API_KEY"); + +// [START onCreateTrigger] +exports.sendWelcomeEmail = onUserCreated({secrets: [emailApiKey]}, async (event) => { +// [END onCreateTrigger] + // [START eventAttributes] + const {email, displayName} = event.data; + // [END eventAttributes] + + await sendWelcomeEmail(email, displayName); +}); +// [END sendWelcomeEmail] diff --git a/Node/quickstarts/email-users/functions/tenants.js b/Node/quickstarts/email-users/functions/tenants.js new file mode 100644 index 0000000000..3dd04b81fa --- /dev/null +++ b/Node/quickstarts/email-users/functions/tenants.js @@ -0,0 +1,48 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +"use strict"; + +// [START multitenancy] +const {onUserCreated, IS_NOT_TENANT} = require("firebase-functions/identity"); +const {defineSecret} = require("firebase-functions/params"); +const {sendWelcomeEmail} = require("./utils/myEmailService"); + +const emailApiKey = defineSecret("EMAIL_API_KEY"); + +/** + * Sends a welcome email scoped to a specific tenant in Identity Platform. + */ +exports.sendWelcomeEmailToTenant = onUserCreated( + {secrets: [emailApiKey], tenantId: "my-tenant-id"}, + async (event) => { + const {email, displayName} = event.data; + + await sendWelcomeEmail(email, displayName, "my-tenant-id"); + }, +); + +/** + * Sends a welcome email only to users not associated with any tenant. + */ +exports.sendWelcomeEmailNoTenant = onUserCreated( + {secrets: [emailApiKey], tenantId: IS_NOT_TENANT}, + async (event) => { + const {email, displayName} = event.data; + + await sendWelcomeEmail(email, displayName); + }, +); +// [END multitenancy] diff --git a/Node/quickstarts/email-users/functions/utils/myEmailService.js b/Node/quickstarts/email-users/functions/utils/myEmailService.js new file mode 100644 index 0000000000..da95f16834 --- /dev/null +++ b/Node/quickstarts/email-users/functions/utils/myEmailService.js @@ -0,0 +1,84 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +"use strict"; + +// [START myEmailService] +const {logger} = require("firebase-functions"); +const {defineSecret} = require("firebase-functions/params"); +const {Resend} = require("resend"); + +const emailApiKey = defineSecret("EMAIL_API_KEY"); + +/** + * Sends an email using the Resend API. + * @param {Object} options Email sending options. + * @param {string} options.to Recipient email address. + * @param {string} options.subject Email subject line. + * @param {string} options.text Plaintext email body. + * @return {Promise} + */ +async function sendEmail({to, subject, text}) { + const resend = new Resend(emailApiKey.value()); + await resend.emails.send({ + from: "My Firebase demo ", + to, + subject, + text, + }); +} + +/** + * Sends a welcome email to a new user. + * @param {string} email User email address. + * @param {string} [displayName] User display name. + * @param {string} [tenantId] Optional tenant ID to customize the tone. + * @return {Promise} + */ +async function sendWelcomeEmail(email, displayName, tenantId) { + const greeting = tenantId ? "Welcome, new customer" : "Hey"; + const name = displayName ? ` ${displayName}` : ""; + await sendEmail({ + to: email, + subject: "Welcome!", + text: `${greeting}${name}, welcome!`, + }); + logger.log("New welcome email sent to:", email); +} + +/** + * Sends an account deletion confirmation email to a user. + * @param {string} email User email address. + * @param {string} [displayName] User display name. + * @param {string} [tenantId] Optional tenant ID to customize the tone. + * @return {Promise} + */ +async function sendGoodbyeEmail(email, displayName, tenantId) { + const greeting = tenantId ? "Welcome, new customer" : "Hey"; + const name = displayName ? ` ${displayName}` : ""; + await sendEmail({ + to: email, + subject: "Bye!", + text: `${greeting}${name}!, We confirm that we have deleted your My Firebase demo account.`, + }); + logger.log("Account deletion confirmation email sent to:", email); +} + +module.exports = { + sendEmail, + sendWelcomeEmail, + sendGoodbyeEmail, +}; +// [END myEmailService] diff --git a/Node/quickstarts/email-users/public/firebase-logo.png b/Node/quickstarts/email-users/public/firebase-logo.png deleted file mode 100644 index c498b958bb02961ead9bb0f2637ac2f6248af02d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3542 zcmZu!cRU+h_Ybj)Qq-)(s1YPKNkxg3*n1UELhKQ<_D(fMZKbMec2Kj_Xw@E7)U2j8 zN~`stMT}Zc`|^zUeShPAKKGt`&i$V8J?F2RY-+5>!o7WQcGf_(E}bg0m|$ z%-8=E4FITxAx^KpuE98Pn6Hmt5F!jI{3il&dOtJ6gu#D8g1wQ#wnnC4Ekd9xSP7~K zl@(TJ0)xS7fp|BBxwh`#?xz}3*dsXD9|41fhK52z6`+JbcbFU;4u{Fg!{p^ zA1n;#50it+!u~&}U{AOIl=nZp&R+g<`;*mQ$<$6;gU||e#RU@rEeQl4bHbfOj&E74B0J{|&*^Cpb1BAqGTE8tLyG7FqtZ)s zh>R;g0>;R%2{qAUbFaV6(aVkpD}>&U9wTS$eIwK~&o0oHd80@0*=|KbFv-|QwTukv z#TYpdMd#o%`6eA&{`h3=ry__P!P6;XejO_hl2EWwIT*&d#w|pIlgbQ!E4!R?e)5;o zVWIrC!$8MzoJSi1+}n`by$i6rR^d}TWQ-gW4-LI@Q}W}dE9M}!^lHWFh6e4i7%yd` zTz3PbewpB=hml%Du0TCf1) zGwt&;Vyf;*|5z5+*m74Jj?csHvkM;fmB@vuuGw}w_eSkL>!9Q`QrF~M&M|%X{@6){ z6~9t{jV5tNYUmvoCnFqn&X#eju4Y{4M)F_?aoddk=zPn(Zriq2ZsZR(13P;G0qfA3 zJ&647mx(ESEv?}v4GHC7{ykLD_jmSl+WZWe=aPFjw4!$RuVAHg^5j}iM4Jscls<{O zitcRL%HA)DB1P4E>O|j|Ib5^+_;UGK0GQup2KzjHQr2lA;x#7nd>3lU+@rK-W%D(| z$?@n1iQ(uwZf1mw=gT(K)fJl@^Xc51X8tvu+!4eCVW-9_<@QY^f@()0dT@7`zRmr z6=k6suw>JJ=Y-o6!P!Y0%6wBcwQ&?V)X9I8HcXVzoNJ6|ijDE(=Ypo2TGS>k0Hc*# zbk=jjH9G3Dl<4rO*FGPZJTH{fzyCgrx!WwBo#zRd%uxNh^6|_Pz5yw51dv*zzlrS- zZszP7o#rq%@zK#NiumZqH24cWdT0#szg>vum0!=qvEq~D$6gI3@;#OVzZPI)8k4!v z+6m%7Nj)dnJko-1%figO{!Y;#dH)(M&2wA>d|$A<+m4qY?Q3dmjfnr z;8Kux#ButkxZ}f_WA4XuELAox8cllm?hOcY=bQ{7ovQgloE!jGid?Ed`$fT;2HYkp zNZ~Vgngs>vbH_MLGGcRnHSj}}x376wu`RC@U1J6GZFx}!^Tm6W1AUo?T20e9DfTo1 zHB8!3F(wLCFEJ|?^^Dt5TW+2-Vz-##ri5ix5A&u=CwOh7y8go8ZGAbpC#wVC- zX(jS@A0@?05mm6FBMMCy!0%PEI-gAySjNMNay*nvUhcYx%LV7RfY(r1h$qZis$mfE zSUnTCf&`Fc+1|7{;UAPPP2}EHUDDDwp0EGhYoN#NF5v+ry)-UhRI?%We*iQpYl_+5 z#3Wl;avCznB&Kbs&7uJC4TR>C>EyBFX_x-MwGpj!v=5NN2ux8(DqG7P&F+lKESv>u zSH-znrtxNrgO|NM-&Se~7115+IxQth+}3<;)p(TF$CBFi!x~#?J;vLPW{$=8D?g~n z)+g$f(aZN^z35)H5=5F87Ib3xz$lL#8!GBO(Y8B!c10xORea2G`Hy&8ol z%Y3VtoBTBPU<$yH6gJ+YY(kd~AYk~OP%o*_$`ZiyH?1|+3LK$iuFx%u`Tl;Sni z&|KnQsCwF2rwo|y37dsBuzb^TYtUpfS8_Ae$Jl{HXyUxOgI+E3L_$}sJ1is5@%?ym z@0|yb6NA?oP@PIpNzli|G5~L^M@hYb{ zYGnCk*=3iYsI`314M#ow#=ZSeP7dK*HY&%I_+4!I#YiZ;^HH%EkSwU8h`XAgb1S(_ z;O#Bx-F4kKHZv=SM>$U}s|)33AZJ$(LICikxOWT-z;5B|yvhX^cJz3qu3L%q9S)7O zkA#>v3v?PoCyl?MjOB!$CnIerFNd3zMd;_g%y4ATCZ-402g&x3rSC3iy1yG$y7;^e zZfV)Tu(HDRSRl+s^F=w@z36KhN4XNx#tK~2{mOZ{#i)tZUDWxbkGRr?Y)$XHU;lva zYBWx7hJ2r1n}V*zUWjFVxiOtYa-34p0iZ^XE`87Il&6L2Rtikyb$o2<6 z74>>wlp#rWZ2qsH1gK%X21*2qx5$Ss{(rdh&A6J4phlV3Gp-`3(7 zdqZVVMKQz&9Q$Z$S#-jmUD-x3#h{ZKrQ0beRuRoCEJhcf90om)LwY_ER;sG3N@E6% zs5<8p=JC&DMC!O6CQ<3h`mBpxWmj(-W`h{1S%rCM=zJw?>cjn zZ;3R31tuT%x)_Ufw8>X9(a^iRk8XYPhkO4H*`-}WW`Bx@Sqn3gy5wqFbNSvF7i5^i zig6O(qa})pD>Ai)x`qoyM^1mKTnSk6S@Q5}JP1Z0e$p9Uw(}-hV*KOGwfzL0>F8vi!;@n(Rn4TI%;#8zSE~ zeDVE{u+bK$yW`N$<>NmaOq;ZhpJXY~ho2kM!`-h+RT^P_N4iP=3;g1q)j8Q}tpn>y3g+-)KpC<(#`!AY zS9@n&q|Q!DM^hVjA`PxfWb+8DBS&hN?|LwPRqxsiwUc4On-SKm+nyzZ8F x>>&cc4lOJCrO2L>Lst%ygz|w7B)Q?^gR3$(wijKRxl+zPTNoW - - - - - - - - Welcome email demo - - - - - - - - - -
- - -
-
-
-

Welcome email demo

-
-
-
-
-
- - -
-
-

- This web application demonstrates how you can send a welcome email for new accounts. After signing-in for the first time you will receive a welcome email. - Now sign in! -

- -
-
- - -
-
-

- Welcome
- Your Firebase User ID is:
- Your profile picture: -

- - -
-
-
-
-
- - - - - - - - - - - diff --git a/Node/quickstarts/email-users/public/main.css b/Node/quickstarts/email-users/public/main.css deleted file mode 100644 index b9d21d0491..0000000000 --- a/Node/quickstarts/email-users/public/main.css +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2016 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -html, body { - font-family: 'Roboto', 'Helvetica', sans-serif; -} -.mdl-grid { - max-width: 1024px; - margin: auto; -} -.mdl-card { - min-height: 0; - padding-bottom: 5px; -} -.mdl-layout__header-row { - padding: 0; -} -h3 { - background: url('firebase-logo.png') no-repeat; - background-size: 40px; - padding-left: 50px; -} -#demo-signed-out-card, -#demo-signed-in-card { - display: none; -} -#demo-profile-pic { - height: 60px; - width: 60px; - border-radius: 30px; - margin-left: calc(50% - 30px); - margin-top: 10px; -} -#demo-name-container, -#demo-email-container, -#demo-uid-container { - font-weight: bold; -} -#demo-delete-button { - margin-left: 20px; -} diff --git a/Node/quickstarts/email-users/public/main.js b/Node/quickstarts/email-users/public/main.js deleted file mode 100644 index 71dcde9626..0000000000 --- a/Node/quickstarts/email-users/public/main.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2016 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -'use strict'; - -// Initializes the Demo. -function Demo() { - document.addEventListener('DOMContentLoaded', function() { - // Shortcuts to DOM Elements. - this.signInButton = document.getElementById('demo-sign-in-button'); - this.signOutButton = document.getElementById('demo-sign-out-button'); - this.nameContainer = document.getElementById('demo-name-container'); - this.uidContainer = document.getElementById('demo-uid-container'); - this.deleteButton = document.getElementById('demo-delete-button'); - this.profilePic = document.getElementById('demo-profile-pic'); - this.signedOutCard = document.getElementById('demo-signed-out-card'); - this.signedInCard = document.getElementById('demo-signed-in-card'); - - // Bind events. - this.signInButton.addEventListener('click', this.signIn.bind(this)); - this.signOutButton.addEventListener('click', this.signOut.bind(this)); - this.deleteButton.addEventListener('click', this.deleteAccount.bind(this)); - firebase.auth().onAuthStateChanged(this.onAuthStateChanged.bind(this)); - }.bind(this)); -} - -// Triggered on Firebase auth state change. -Demo.prototype.onAuthStateChanged = function(user) { - if (user) { - this.nameContainer.innerText = user.displayName; - this.uidContainer.innerText = user.uid; - this.profilePic.src = user.photoURL; - this.signedOutCard.style.display = 'none'; - this.signedInCard.style.display = 'block'; - } else { - this.signedOutCard.style.display = 'block'; - this.signedInCard.style.display = 'none'; - } -}; - -// Initiates the sign-in flow using LinkedIn sign in in a popup. -Demo.prototype.signIn = function() { - firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider()); -}; - -// Signs-out of Firebase. -Demo.prototype.signOut = function() { - firebase.auth().signOut(); -}; - -// Deletes the user's account. -Demo.prototype.deleteAccount = function() { - firebase.auth().currentUser.delete().then(function() { - window.alert('Account deleted. Check your email for a confirmation.'); - }).catch(function(error) { - if (error.code === 'auth/requires-recent-login') { - window.alert('You need to have recently signed-in to delete your account. Please sign-in and try again.'); - firebase.auth().signOut(); - } - }); -}; - -// Load the demo. -window.demo = new Demo(); From b050aff7bbb5762f6411a573d1f15bf0aae15bad Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:29:54 +0000 Subject: [PATCH 03/12] run the formatter --- Node/quickstarts/email-users/README.md | 7 ---- Node/quickstarts/email-users/firebase.json | 4 +-- .../email-users/functions/eslint.config.js | 6 ++-- .../email-users/functions/index.js | 4 +-- .../email-users/functions/sendByeEmail.js | 19 +++++----- .../email-users/functions/sendWelcomeEmail.js | 23 ++++++------ .../email-users/functions/tenants.js | 36 ++++++++++++------- .../functions/utils/myEmailService.js | 8 ++--- 8 files changed, 57 insertions(+), 50 deletions(-) diff --git a/Node/quickstarts/email-users/README.md b/Node/quickstarts/email-users/README.md index 76b306cb12..244fb9fe82 100644 --- a/Node/quickstarts/email-users/README.md +++ b/Node/quickstarts/email-users/README.md @@ -2,7 +2,6 @@ This quickstart demonstrates how to setup an Auth triggered Cloud Function using the **Firebase SDK for Cloud Functions** and [Resend](https://resend.com/). - ## Introduction We'll deploy 2nd gen Auth triggered functions that send a welcome email when a new user signs up and a goodbye email when user accounts are deleted. @@ -11,7 +10,6 @@ We'll deploy 2nd gen Auth triggered functions that send a welcome email when a n - [Read more about the Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) - [Connect Cloud Functions to the Emulator](https://firebase.google.com/docs/emulator-suite/connect_functions) - ## Functions Code The functions are organized into separate files for clean, self-contained documentation snippets and modular code: @@ -26,7 +24,6 @@ The functions are organized into separate files for clean, self-contained docume Sending emails is performed using [Resend](https://resend.com/). The dependencies are listed in [functions/package.json](functions/package.json). - ## Set up the sample 1. Clone or download this repo and navigate to `Node/quickstarts/email-users`: @@ -43,7 +40,6 @@ Sending emails is performed using [Resend](https://resend.com/). The dependencie ``` > You can obtain a free API key from [Resend](https://resend.com/api-keys). - ## Run locally with the Firebase Emulator Suite The [Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) lets you test Auth triggers and create/delete users directly in the Emulator UI without deploying to a live project. @@ -60,7 +56,6 @@ The [Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-su > `New welcome email sent to: user@example.com` > `Account deletion confirmation email sent to: user@example.com` - ## Deploy to production To deploy the functions to a live Firebase project: @@ -78,12 +73,10 @@ To deploy the functions to a live Firebase project: firebase deploy --only functions ``` - ## Contributing We'd love that you contribute to the project. Before doing so please read our [Contributor guide](../../CONTRIBUTING.md). - ## License © Google, 2026. Licensed under an [Apache-2](../../LICENSE) license. diff --git a/Node/quickstarts/email-users/firebase.json b/Node/quickstarts/email-users/firebase.json index 51af2f6845..2e424ce1fb 100644 --- a/Node/quickstarts/email-users/firebase.json +++ b/Node/quickstarts/email-users/firebase.json @@ -1,9 +1,7 @@ { "functions": { "codebase": "email-users", - "predeploy": [ - "npm --prefix \"$RESOURCE_DIR\" run lint" - ] + "predeploy": ["npm --prefix \"$RESOURCE_DIR\" run lint"] }, "emulators": { "auth": { diff --git a/Node/quickstarts/email-users/functions/eslint.config.js b/Node/quickstarts/email-users/functions/eslint.config.js index 80492c6b15..cb0659cccf 100644 --- a/Node/quickstarts/email-users/functions/eslint.config.js +++ b/Node/quickstarts/email-users/functions/eslint.config.js @@ -26,7 +26,7 @@ module.exports = [ "no-prototype-builtins": "off", "no-redeclare": "off", "no-constant-condition": "off", - "no-case-declarations": "off" - } - } + "no-case-declarations": "off", + }, + }, ]; diff --git a/Node/quickstarts/email-users/functions/index.js b/Node/quickstarts/email-users/functions/index.js index 5d69d4358d..01662d9685 100644 --- a/Node/quickstarts/email-users/functions/index.js +++ b/Node/quickstarts/email-users/functions/index.js @@ -15,8 +15,8 @@ */ "use strict"; -const {sendWelcomeEmail} = require("./sendWelcomeEmail"); -const {sendByeEmail} = require("./sendByeEmail"); +const { sendWelcomeEmail } = require("./sendWelcomeEmail"); +const { sendByeEmail } = require("./sendByeEmail"); const { sendWelcomeEmailToTenant, sendWelcomeEmailNoTenant, diff --git a/Node/quickstarts/email-users/functions/sendByeEmail.js b/Node/quickstarts/email-users/functions/sendByeEmail.js index 8377144b18..a0ab765131 100644 --- a/Node/quickstarts/email-users/functions/sendByeEmail.js +++ b/Node/quickstarts/email-users/functions/sendByeEmail.js @@ -16,17 +16,20 @@ "use strict"; // [START sendByeEmail] -const {onUserDeleted} = require("firebase-functions/identity"); -const {defineSecret} = require("firebase-functions/params"); -const {sendGoodbyeEmail} = require("./utils/myEmailService"); +const { onUserDeleted } = require("firebase-functions/identity"); +const { defineSecret } = require("firebase-functions/params"); +const { sendGoodbyeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); // [START onDeleteTrigger] -exports.sendByeEmail = onUserDeleted({secrets: [emailApiKey]}, async (event) => { -// [END onDeleteTrigger] - const {email, displayName} = event.data; +exports.sendByeEmail = onUserDeleted( + { secrets: [emailApiKey] }, + async (event) => { + // [END onDeleteTrigger] + const { email, displayName } = event.data; - await sendGoodbyeEmail(email, displayName); -}); + await sendGoodbyeEmail(email, displayName); + }, +); // [END sendByeEmail] diff --git a/Node/quickstarts/email-users/functions/sendWelcomeEmail.js b/Node/quickstarts/email-users/functions/sendWelcomeEmail.js index 52c6cbe013..9f68f46176 100644 --- a/Node/quickstarts/email-users/functions/sendWelcomeEmail.js +++ b/Node/quickstarts/email-users/functions/sendWelcomeEmail.js @@ -16,19 +16,22 @@ "use strict"; // [START sendWelcomeEmail] -const {onUserCreated} = require("firebase-functions/identity"); -const {defineSecret} = require("firebase-functions/params"); -const {sendWelcomeEmail} = require("./utils/myEmailService"); +const { onUserCreated } = require("firebase-functions/identity"); +const { defineSecret } = require("firebase-functions/params"); +const { sendWelcomeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); // [START onCreateTrigger] -exports.sendWelcomeEmail = onUserCreated({secrets: [emailApiKey]}, async (event) => { -// [END onCreateTrigger] - // [START eventAttributes] - const {email, displayName} = event.data; - // [END eventAttributes] +exports.sendWelcomeEmail = onUserCreated( + { secrets: [emailApiKey] }, + async (event) => { + // [END onCreateTrigger] + // [START eventAttributes] + const { email, displayName } = event.data; + // [END eventAttributes] - await sendWelcomeEmail(email, displayName); -}); + await sendWelcomeEmail(email, displayName); + }, +); // [END sendWelcomeEmail] diff --git a/Node/quickstarts/email-users/functions/tenants.js b/Node/quickstarts/email-users/functions/tenants.js index 3dd04b81fa..e8a1475c56 100644 --- a/Node/quickstarts/email-users/functions/tenants.js +++ b/Node/quickstarts/email-users/functions/tenants.js @@ -16,9 +16,9 @@ "use strict"; // [START multitenancy] -const {onUserCreated, IS_NOT_TENANT} = require("firebase-functions/identity"); -const {defineSecret} = require("firebase-functions/params"); -const {sendWelcomeEmail} = require("./utils/myEmailService"); +const { onUserCreated, IS_NOT_TENANT } = require("firebase-functions/identity"); +const { defineSecret } = require("firebase-functions/params"); +const { sendWelcomeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); @@ -26,23 +26,33 @@ const emailApiKey = defineSecret("EMAIL_API_KEY"); * Sends a welcome email scoped to a specific tenant in Identity Platform. */ exports.sendWelcomeEmailToTenant = onUserCreated( - {secrets: [emailApiKey], tenantId: "my-tenant-id"}, - async (event) => { - const {email, displayName} = event.data; + { + secrets: [emailApiKey], + // Only trigger when a user is a member of this tenant + tenantId: "my-tenant-id", + }, + async (event) => { + const { email, displayName } = event.data; - await sendWelcomeEmail(email, displayName, "my-tenant-id"); - }, + // Customize the email for this tenant + await sendWelcomeEmail(email, displayName, "my-tenant-id"); + }, ); /** * Sends a welcome email only to users not associated with any tenant. */ exports.sendWelcomeEmailNoTenant = onUserCreated( - {secrets: [emailApiKey], tenantId: IS_NOT_TENANT}, - async (event) => { - const {email, displayName} = event.data; + { + secrets: [emailApiKey], + // Only trigger when a user is NOT a member of a tenant + tenantId: IS_NOT_TENANT, + }, + async (event) => { + const { email, displayName } = event.data; - await sendWelcomeEmail(email, displayName); - }, + // Send a generic welcome email + await sendWelcomeEmail(email, displayName); + }, ); // [END multitenancy] diff --git a/Node/quickstarts/email-users/functions/utils/myEmailService.js b/Node/quickstarts/email-users/functions/utils/myEmailService.js index da95f16834..5961cc35e8 100644 --- a/Node/quickstarts/email-users/functions/utils/myEmailService.js +++ b/Node/quickstarts/email-users/functions/utils/myEmailService.js @@ -16,9 +16,9 @@ "use strict"; // [START myEmailService] -const {logger} = require("firebase-functions"); -const {defineSecret} = require("firebase-functions/params"); -const {Resend} = require("resend"); +const { logger } = require("firebase-functions"); +const { defineSecret } = require("firebase-functions/params"); +const { Resend } = require("resend"); const emailApiKey = defineSecret("EMAIL_API_KEY"); @@ -30,7 +30,7 @@ const emailApiKey = defineSecret("EMAIL_API_KEY"); * @param {string} options.text Plaintext email body. * @return {Promise} */ -async function sendEmail({to, subject, text}) { +async function sendEmail({ to, subject, text }) { const resend = new Resend(emailApiKey.value()); await resend.emails.send({ from: "My Firebase demo ", From 08d90bed7a94c417a495444b74320576d3c606fb Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:49:17 +0000 Subject: [PATCH 04/12] handle missing email --- .../email-users/functions/sendByeEmail.js | 7 ++++++- .../email-users/functions/sendWelcomeEmail.js | 8 +++++++- Node/quickstarts/email-users/functions/tenants.js | 13 +++++++++++-- .../email-users/functions/utils/myEmailService.js | 13 +++++++++++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Node/quickstarts/email-users/functions/sendByeEmail.js b/Node/quickstarts/email-users/functions/sendByeEmail.js index a0ab765131..cf6ff9027c 100644 --- a/Node/quickstarts/email-users/functions/sendByeEmail.js +++ b/Node/quickstarts/email-users/functions/sendByeEmail.js @@ -18,6 +18,7 @@ // [START sendByeEmail] const { onUserDeleted } = require("firebase-functions/identity"); const { defineSecret } = require("firebase-functions/params"); +const { logger } = require("firebase-functions"); const { sendGoodbyeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); @@ -27,7 +28,11 @@ exports.sendByeEmail = onUserDeleted( { secrets: [emailApiKey] }, async (event) => { // [END onDeleteTrigger] - const { email, displayName } = event.data; + const { uid, email, displayName } = event.data; + if (!email) { + logger.log(`User ${uid} does not have an email address.`); + return; + } await sendGoodbyeEmail(email, displayName); }, diff --git a/Node/quickstarts/email-users/functions/sendWelcomeEmail.js b/Node/quickstarts/email-users/functions/sendWelcomeEmail.js index 9f68f46176..90f3a56df7 100644 --- a/Node/quickstarts/email-users/functions/sendWelcomeEmail.js +++ b/Node/quickstarts/email-users/functions/sendWelcomeEmail.js @@ -18,6 +18,7 @@ // [START sendWelcomeEmail] const { onUserCreated } = require("firebase-functions/identity"); const { defineSecret } = require("firebase-functions/params"); +const { logger } = require("firebase-functions"); const { sendWelcomeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); @@ -28,9 +29,14 @@ exports.sendWelcomeEmail = onUserCreated( async (event) => { // [END onCreateTrigger] // [START eventAttributes] - const { email, displayName } = event.data; + const { uid, email, displayName } = event.data; // [END eventAttributes] + if (!email) { + logger.log(`User ${uid} does not have an email address.`); + return; + } + await sendWelcomeEmail(email, displayName); }, ); diff --git a/Node/quickstarts/email-users/functions/tenants.js b/Node/quickstarts/email-users/functions/tenants.js index e8a1475c56..6f349c2067 100644 --- a/Node/quickstarts/email-users/functions/tenants.js +++ b/Node/quickstarts/email-users/functions/tenants.js @@ -18,6 +18,7 @@ // [START multitenancy] const { onUserCreated, IS_NOT_TENANT } = require("firebase-functions/identity"); const { defineSecret } = require("firebase-functions/params"); +const { logger } = require("firebase-functions"); const { sendWelcomeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); @@ -32,7 +33,11 @@ exports.sendWelcomeEmailToTenant = onUserCreated( tenantId: "my-tenant-id", }, async (event) => { - const { email, displayName } = event.data; + const { uid, email, displayName } = event.data; + if (!email) { + logger.log(`User ${uid} does not have an email address.`); + return; + } // Customize the email for this tenant await sendWelcomeEmail(email, displayName, "my-tenant-id"); @@ -49,7 +54,11 @@ exports.sendWelcomeEmailNoTenant = onUserCreated( tenantId: IS_NOT_TENANT, }, async (event) => { - const { email, displayName } = event.data; + const { uid, email, displayName } = event.data; + if (!email) { + logger.log(`user ${uid} does not have an email address.`); + return; + } // Send a generic welcome email await sendWelcomeEmail(email, displayName); diff --git a/Node/quickstarts/email-users/functions/utils/myEmailService.js b/Node/quickstarts/email-users/functions/utils/myEmailService.js index 5961cc35e8..c2598ff596 100644 --- a/Node/quickstarts/email-users/functions/utils/myEmailService.js +++ b/Node/quickstarts/email-users/functions/utils/myEmailService.js @@ -31,6 +31,9 @@ const emailApiKey = defineSecret("EMAIL_API_KEY"); * @return {Promise} */ async function sendEmail({ to, subject, text }) { + if (!to) { + throw new Error('Missing email address.'); + } const resend = new Resend(emailApiKey.value()); await resend.emails.send({ from: "My Firebase demo ", @@ -48,7 +51,10 @@ async function sendEmail({ to, subject, text }) { * @return {Promise} */ async function sendWelcomeEmail(email, displayName, tenantId) { - const greeting = tenantId ? "Welcome, new customer" : "Hey"; + if (!email) { + return; + } + const greeting = tenantId ? `Hello member of ${tenantId}` : "Hey"; const name = displayName ? ` ${displayName}` : ""; await sendEmail({ to: email, @@ -66,7 +72,10 @@ async function sendWelcomeEmail(email, displayName, tenantId) { * @return {Promise} */ async function sendGoodbyeEmail(email, displayName, tenantId) { - const greeting = tenantId ? "Welcome, new customer" : "Hey"; + if (!email) { + return; + } + const greeting = tenantId ? `Goodbye member of ${tenantId}` : "Bye"; const name = displayName ? ` ${displayName}` : ""; await sendEmail({ to: email, From 35bd9c72222a7a4d90aa95ee6c165e0446a92b43 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:54:24 +0000 Subject: [PATCH 05/12] regen lockfile --- .../email-users/functions/pnpm-lock.yaml | 399 ++++++++++++------ 1 file changed, 279 insertions(+), 120 deletions(-) diff --git a/Node/quickstarts/email-users/functions/pnpm-lock.yaml b/Node/quickstarts/email-users/functions/pnpm-lock.yaml index 7da12a0ae2..01bcb95e62 100644 --- a/Node/quickstarts/email-users/functions/pnpm-lock.yaml +++ b/Node/quickstarts/email-users/functions/pnpm-lock.yaml @@ -10,20 +10,20 @@ importers: dependencies: firebase-admin: specifier: ^14.2.0 - version: 14.2.0 + version: 14.2.0(supports-color@7.2.0) firebase-functions: specifier: ^7.3.2 - version: 7.3.2(firebase-admin@14.2.0) - nodemailer: - specifier: ^8.0.11 - version: 8.0.11 + version: 7.3.2(firebase-admin@14.2.0(supports-color@7.2.0))(supports-color@7.2.0) + resend: + specifier: ^4.0.0 + version: 4.8.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) devDependencies: eslint: specifier: ^8.57.1 - version: 8.57.1 - eslint-plugin-promise: - specifier: ^7.3.0 - version: 7.3.0(eslint@8.57.1) + version: 8.57.1(supports-color@7.2.0) + eslint-config-google: + specifier: ^0.14.0 + version: 0.14.0(eslint@8.57.1(supports-color@7.2.0)) packages: @@ -179,6 +179,16 @@ packages: '@protobufjs/utf8@1.1.2': resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + '@react-email/render@1.1.2': + resolution: {integrity: sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^18.0 || ^19.0 || ^19.0.0-rc + + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@tootallnate/once@2.0.1': resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} engines: {node: '>= 10'} @@ -399,6 +409,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -411,6 +425,19 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -440,6 +467,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -467,11 +498,11 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-promise@7.3.0: - resolution: {integrity: sha512-6uGiOR0INuujr6PEQmeSSP7GbIMJ/ebEXXiEzb/nOj68LknH5Pxzb/AbZivmr6VE6TkTE8rTjRK9zhKpK6HsRA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-config-google@0.14.0: + resolution: {integrity: sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==} + engines: {node: '>=0.10.0'} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + eslint: '>=5.16.0' eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} @@ -522,6 +553,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -729,6 +763,13 @@ packages: html-entities@2.6.0: resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -847,6 +888,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -973,10 +1017,6 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - nodemailer@8.0.11: - resolution: {integrity: sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==} - engines: {node: '>=6.0.0'} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1015,6 +1055,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1042,10 +1085,18 @@ packages: path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + proto3-json-serializer@3.0.4: resolution: {integrity: sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==} engines: {node: '>=18'} @@ -1077,6 +1128,18 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + react-dom@19.3.0: + resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} + peerDependencies: + react: ^19.3.0 + + react-promise-suspense@0.3.4: + resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} + + react@19.3.0: + resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} + engines: {node: '>=0.10.0'} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -1085,6 +1148,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + resend@4.8.0: + resolution: {integrity: sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA==} + engines: {node: '>=18'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1127,6 +1194,12 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -1329,17 +1402,17 @@ packages: snapshots: - '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1(supports-color@7.2.0))': dependencies: - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/eslintrc@2.1.4': + '@eslint/eslintrc@2.1.4(supports-color@7.2.0)': dependencies: ajv: 6.15.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -1399,12 +1472,12 @@ snapshots: dependencies: tslib: 2.8.1 - '@google-cloud/firestore@8.7.0': + '@google-cloud/firestore@8.7.0(supports-color@7.2.0)': dependencies: '@opentelemetry/api': 1.9.1 fast-deep-equal: 3.1.3 functional-red-black-tree: 1.0.1 - google-gax: 5.0.8 + google-gax: 5.0.8(supports-color@7.2.0) protobufjs: 7.6.5 transitivePeerDependencies: - supports-color @@ -1422,7 +1495,7 @@ snapshots: '@google-cloud/promisify@4.0.0': optional: true - '@google-cloud/storage@7.21.0': + '@google-cloud/storage@7.21.0(supports-color@7.2.0)': dependencies: '@google-cloud/paginator': 5.0.2 '@google-cloud/projectify': 4.0.0 @@ -1431,13 +1504,13 @@ snapshots: async-retry: 1.3.3 duplexify: 4.1.3 fast-xml-parser: 5.10.1 - gaxios: 6.7.1 - google-auth-library: 9.15.1 + gaxios: 6.7.1(supports-color@7.2.0) + google-auth-library: 9.15.1(supports-color@7.2.0) html-entities: 2.6.0 mime: 3.0.0 p-limit: 3.1.0 - retry-request: 7.0.2 - teeny-request: 9.0.0 + retry-request: 7.0.2(supports-color@7.2.0) + teeny-request: 9.0.0(supports-color@7.2.0) transitivePeerDependencies: - encoding - supports-color @@ -1457,10 +1530,10 @@ snapshots: yargs: 17.7.3 optional: true - '@humanwhocodes/config-array@0.13.0': + '@humanwhocodes/config-array@0.13.0(supports-color@7.2.0)': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -1523,6 +1596,19 @@ snapshots: '@protobufjs/utf8@1.1.2': {} + '@react-email/render@1.1.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + html-to-text: 9.0.5 + prettier: 3.9.6 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-promise-suspense: 0.3.4 + + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@tootallnate/once@2.0.1': optional: true @@ -1610,9 +1696,9 @@ snapshots: acorn@8.18.0: {} - agent-base@6.0.2: + agent-base@6.0.2(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color optional: true @@ -1660,11 +1746,11 @@ snapshots: bignumber.js@9.3.1: {} - body-parser@2.3.0: + body-parser@2.3.0(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 @@ -1748,12 +1834,16 @@ snapshots: data-uri-to-buffer@4.0.1: {} - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 deep-is@0.1.4: {} + deepmerge@4.3.1: {} + delayed-stream@1.0.0: optional: true @@ -1763,6 +1853,24 @@ snapshots: dependencies: esutils: 2.0.3 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -1799,6 +1907,8 @@ snapshots: once: 1.4.0 optional: true + entities@4.5.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -1822,10 +1932,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-promise@7.3.0(eslint@8.57.1): + eslint-config-google@0.14.0(eslint@8.57.1(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) eslint-scope@7.2.2: dependencies: @@ -1834,20 +1943,20 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint@8.57.1: + eslint@8.57.1(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 + '@eslint/eslintrc': 2.1.4(supports-color@7.2.0) '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/config-array': 0.13.0(supports-color@7.2.0) '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.3.3 ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -1900,20 +2009,20 @@ snapshots: event-target-shim@5.0.1: optional: true - express@5.2.1: + express@5.2.1(supports-color@7.2.0): dependencies: accepts: 2.0.0 - body-parser: 2.3.0 + body-parser: 2.3.0(supports-color@7.2.0) content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@7.2.0) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -1924,9 +2033,9 @@ snapshots: proxy-addr: 2.0.7 qs: 6.15.3 range-parser: 1.3.0 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 + router: 2.2.0(supports-color@7.2.0) + send: 1.2.1(supports-color@7.2.0) + serve-static: 2.2.1(supports-color@7.2.0) statuses: 2.0.2 type-is: 2.1.0 vary: 1.1.2 @@ -1935,6 +2044,8 @@ snapshots: extend@3.0.2: {} + fast-deep-equal@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -1974,9 +2085,9 @@ snapshots: dependencies: flat-cache: 3.2.0 - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -1990,29 +2101,29 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - firebase-admin@14.2.0: + firebase-admin@14.2.0(supports-color@7.2.0): dependencies: '@fastify/busboy': 3.2.0 '@firebase/database-compat': 2.1.4 '@firebase/database-types': 1.0.20 fast-deep-equal: 3.1.3 - google-auth-library: 10.9.1 + google-auth-library: 10.9.1(supports-color@7.2.0) jsonwebtoken: 9.0.3 - jwks-rsa: 4.1.0 + jwks-rsa: 4.1.0(supports-color@7.2.0) optionalDependencies: - '@google-cloud/firestore': 8.7.0 - '@google-cloud/storage': 7.21.0 + '@google-cloud/firestore': 8.7.0(supports-color@7.2.0) + '@google-cloud/storage': 7.21.0(supports-color@7.2.0) transitivePeerDependencies: - encoding - supports-color - firebase-functions@7.3.2(firebase-admin@14.2.0): + firebase-functions@7.3.2(firebase-admin@14.2.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@types/cors': 2.8.19 '@types/express': 5.0.6 cors: 2.8.6 - express: 5.2.1 - firebase-admin: 14.2.0 + express: 5.2.1(supports-color@7.2.0) + firebase-admin: 14.2.0(supports-color@7.2.0) protobufjs: 7.6.5 transitivePeerDependencies: - supports-color @@ -2056,10 +2167,10 @@ snapshots: functional-red-black-tree@1.0.1: optional: true - gaxios@6.7.1: + gaxios@6.7.1(supports-color@7.2.0): dependencies: extend: 3.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@7.2.0) is-stream: 2.0.1 node-fetch: 2.7.0 uuid: 9.0.1 @@ -2068,27 +2179,27 @@ snapshots: - supports-color optional: true - gaxios@7.1.3: + gaxios@7.1.3(supports-color@7.2.0): dependencies: extend: 3.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@7.2.0) node-fetch: 3.3.2 rimraf: 5.0.10 transitivePeerDependencies: - supports-color optional: true - gaxios@7.3.0: + gaxios@7.3.0(supports-color@7.2.0): dependencies: extend: 3.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@7.2.0) node-fetch: 3.3.2 transitivePeerDependencies: - supports-color - gcp-metadata@6.1.1: + gcp-metadata@6.1.1(supports-color@7.2.0): dependencies: - gaxios: 6.7.1 + gaxios: 6.7.1(supports-color@7.2.0) google-logging-utils: 0.0.2 json-bigint: 1.0.0 transitivePeerDependencies: @@ -2096,17 +2207,17 @@ snapshots: - supports-color optional: true - gcp-metadata@8.1.2: + gcp-metadata@8.1.2(supports-color@7.2.0): dependencies: - gaxios: 7.3.0 + gaxios: 7.3.0(supports-color@7.2.0) google-logging-utils: 1.1.3 json-bigint: 1.0.0 transitivePeerDependencies: - supports-color - gcp-metadata@8.1.4: + gcp-metadata@8.1.4(supports-color@7.2.0): dependencies: - gaxios: 7.1.3 + gaxios: 7.1.3(supports-color@7.2.0) google-logging-utils: 1.1.3 json-bigint: 1.0.0 transitivePeerDependencies: @@ -2161,55 +2272,55 @@ snapshots: dependencies: type-fest: 0.20.2 - google-auth-library@10.5.0: + google-auth-library@10.5.0(supports-color@7.2.0): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 7.3.0 - gcp-metadata: 8.1.4 + gaxios: 7.3.0(supports-color@7.2.0) + gcp-metadata: 8.1.4(supports-color@7.2.0) google-logging-utils: 1.1.3 - gtoken: 8.0.0 + gtoken: 8.0.0(supports-color@7.2.0) jws: 4.0.1 transitivePeerDependencies: - supports-color optional: true - google-auth-library@10.9.1: + google-auth-library@10.9.1(supports-color@7.2.0): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 7.3.0 - gcp-metadata: 8.1.2 + gaxios: 7.3.0(supports-color@7.2.0) + gcp-metadata: 8.1.2(supports-color@7.2.0) google-logging-utils: 1.1.3 jws: 4.0.1 transitivePeerDependencies: - supports-color - google-auth-library@9.15.1: + google-auth-library@9.15.1(supports-color@7.2.0): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 6.7.1 - gcp-metadata: 6.1.1 - gtoken: 7.1.0 + gaxios: 6.7.1(supports-color@7.2.0) + gcp-metadata: 6.1.1(supports-color@7.2.0) + gtoken: 7.1.0(supports-color@7.2.0) jws: 4.0.1 transitivePeerDependencies: - encoding - supports-color optional: true - google-gax@5.0.8: + google-gax@5.0.8(supports-color@7.2.0): dependencies: '@grpc/grpc-js': 1.14.4 '@grpc/proto-loader': 0.8.1 duplexify: 4.1.3 - google-auth-library: 10.5.0 + google-auth-library: 10.5.0(supports-color@7.2.0) google-logging-utils: 1.1.3 node-fetch: 3.3.2 object-hash: 3.0.0 proto3-json-serializer: 3.0.4 protobufjs: 7.6.5 - retry-request: 8.0.4 + retry-request: 8.0.4(supports-color@7.2.0) rimraf: 5.0.10 transitivePeerDependencies: - supports-color @@ -2224,18 +2335,18 @@ snapshots: graphemer@1.4.0: {} - gtoken@7.1.0: + gtoken@7.1.0(supports-color@7.2.0): dependencies: - gaxios: 6.7.1 + gaxios: 6.7.1(supports-color@7.2.0) jws: 4.0.1 transitivePeerDependencies: - encoding - supports-color optional: true - gtoken@8.0.0: + gtoken@8.0.0(supports-color@7.2.0): dependencies: - gaxios: 7.3.0 + gaxios: 7.3.0(supports-color@7.2.0) jws: 4.0.1 transitivePeerDependencies: - supports-color @@ -2257,6 +2368,21 @@ snapshots: html-entities@2.6.0: optional: true + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -2267,35 +2393,35 @@ snapshots: http-parser-js@0.5.10: {} - http-proxy-agent@5.0.0: + http-proxy-agent@5.0.0(supports-color@7.2.0): dependencies: '@tootallnate/once': 2.0.1 - agent-base: 6.0.2 - debug: 4.4.3 + agent-base: 6.0.2(supports-color@7.2.0) + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color optional: true - http-proxy-agent@7.0.2: + http-proxy-agent@7.0.2(supports-color@7.2.0): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color optional: true - https-proxy-agent@5.0.1: + https-proxy-agent@5.0.1(supports-color@7.2.0): dependencies: - agent-base: 6.0.2 - debug: 4.4.3 + agent-base: 6.0.2(supports-color@7.2.0) + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color optional: true - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@7.2.0): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2384,10 +2510,10 @@ snapshots: ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - jwks-rsa@4.1.0: + jwks-rsa@4.1.0(supports-color@7.2.0): dependencies: '@types/jsonwebtoken': 9.0.10 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) jose: 6.2.5 limiter: 1.1.5 lru-cache: 11.5.2 @@ -2404,6 +2530,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + leac@0.6.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -2502,8 +2630,6 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - nodemailer@8.0.11: {} - object-assign@4.1.1: {} object-hash@3.0.0: @@ -2543,6 +2669,11 @@ snapshots: dependencies: callsites: 3.1.0 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -2562,8 +2693,12 @@ snapshots: path-to-regexp@8.4.2: {} + peberminta@0.9.0: {} + prelude-ls@1.2.1: {} + prettier@3.9.6: {} + proto3-json-serializer@3.0.4: dependencies: protobufjs: 7.6.5 @@ -2606,6 +2741,17 @@ snapshots: iconv-lite: 0.7.3 unpipe: 1.0.0 + react-dom@19.3.0(react@19.3.0): + dependencies: + react: 19.3.0 + scheduler: 0.28.0 + + react-promise-suspense@0.3.4: + dependencies: + fast-deep-equal: 2.0.1 + + react@19.3.0: {} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -2616,22 +2762,29 @@ snapshots: require-directory@2.1.1: optional: true + resend@4.8.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + dependencies: + '@react-email/render': 1.1.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + transitivePeerDependencies: + - react + - react-dom + resolve-from@4.0.0: {} - retry-request@7.0.2: + retry-request@7.0.2(supports-color@7.2.0): dependencies: '@types/request': 2.48.13 extend: 3.0.2 - teeny-request: 9.0.0 + teeny-request: 9.0.0(supports-color@7.2.0) transitivePeerDependencies: - encoding - supports-color optional: true - retry-request@8.0.4: + retry-request@8.0.4(supports-color@7.2.0): dependencies: extend: 3.0.2 - teeny-request: 10.1.4 + teeny-request: 10.1.4(supports-color@7.2.0) transitivePeerDependencies: - supports-color optional: true @@ -2650,9 +2803,9 @@ snapshots: glob: 10.5.0 optional: true - router@2.2.0: + router@2.2.0(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -2668,11 +2821,17 @@ snapshots: safer-buffer@2.1.2: {} + scheduler@0.28.0: {} + + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@7.8.5: {} - send@1.2.1: + send@1.2.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -2686,12 +2845,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.2.1: + serve-static@2.2.1(supports-color@7.2.0): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2786,20 +2945,20 @@ snapshots: dependencies: has-flag: 4.0.0 - teeny-request@10.1.4: + teeny-request@10.1.4(supports-color@7.2.0): dependencies: - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + http-proxy-agent: 7.0.2(supports-color@7.2.0) + https-proxy-agent: 7.0.6(supports-color@7.2.0) node-fetch: 3.3.2 stream-events: 1.0.5 transitivePeerDependencies: - supports-color optional: true - teeny-request@9.0.0: + teeny-request@9.0.0(supports-color@7.2.0): dependencies: - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 + http-proxy-agent: 5.0.0(supports-color@7.2.0) + https-proxy-agent: 5.0.1(supports-color@7.2.0) node-fetch: 2.7.0 stream-events: 1.0.5 uuid: 9.0.1 From 568f747ee46b88d8687a148eb06ba9d43e4c8be5 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Fri, 11 Sep 2026 18:24:20 +0000 Subject: [PATCH 06/12] rename --- .../functions/index.js | 15 ++++++++------- .../{sendByeEmail.js => deletedUserFarewell.js} | 2 +- Node/quickstarts/email-users/functions/index.js | 8 ++++---- .../{sendWelcomeEmail.js => newUserWelcome.js} | 2 +- .../email-users/functions/utils/myEmailService.js | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) rename Node/quickstarts/email-users/functions/{sendByeEmail.js => deletedUserFarewell.js} (96%) rename Node/quickstarts/email-users/functions/{sendWelcomeEmail.js => newUserWelcome.js} (96%) diff --git a/Node/delete-unused-accounts-cron/functions/index.js b/Node/delete-unused-accounts-cron/functions/index.js index 837735a065..e808df7f8a 100644 --- a/Node/delete-unused-accounts-cron/functions/index.js +++ b/Node/delete-unused-accounts-cron/functions/index.js @@ -22,8 +22,9 @@ const {onSchedule} = require("firebase-functions/scheduler"); const {logger} = require("firebase-functions"); // The Firebase Admin SDK to delete inactive users. -const admin = require("firebase-admin"); -admin.initializeApp(); +const {initializeApp} = require("firebase-admin/app"); +const {getAuth} = require("firebase-admin/auth"); +initializeApp(); // The es6-promise-pool to limit the concurrency of promises. const PromisePool = require("es6-promise-pool").default; @@ -52,7 +53,7 @@ exports.accountcleanup = onSchedule("every day 00:00", async (event) => { // [START deleteInactiveUser] /** * Deletes one inactive user from the list. - * @param {admin.auth.UserRecord[]} inactiveUsers + * @param {import("firebase-admin/auth").UserRecord[]} inactiveUsers * @return {null | Promise} */ function deleteInactiveUser(inactiveUsers) { @@ -60,7 +61,7 @@ function deleteInactiveUser(inactiveUsers) { const userToDelete = inactiveUsers.pop(); // Delete the inactive user. - return admin.auth().deleteUser(userToDelete.uid).then(() => { + return getAuth().deleteUser(userToDelete.uid).then(() => { return logger.log( "Deleted user account", userToDelete.uid, @@ -84,12 +85,12 @@ function deleteInactiveUser(inactiveUsers) { // Returns the list of all inactive users. /** * - * @param {admin.auth.UserRecord[]} [users] the current list of inactive users + * @param {import("firebase-admin/auth").UserRecord[]} [users] the current list of inactive users * @param {string} [nextPageToken] - * @return {Promise} + * @return {Promise} */ async function getInactiveUsers(users = [], nextPageToken) { - const result = await admin.auth().listUsers(1000, nextPageToken); + const result = await getAuth().listUsers(1000, nextPageToken); // Find users that have not signed in in the last 30 days. const inactiveUsers = result.users.filter( (user) => diff --git a/Node/quickstarts/email-users/functions/sendByeEmail.js b/Node/quickstarts/email-users/functions/deletedUserFarewell.js similarity index 96% rename from Node/quickstarts/email-users/functions/sendByeEmail.js rename to Node/quickstarts/email-users/functions/deletedUserFarewell.js index cf6ff9027c..c86f62a038 100644 --- a/Node/quickstarts/email-users/functions/sendByeEmail.js +++ b/Node/quickstarts/email-users/functions/deletedUserFarewell.js @@ -24,7 +24,7 @@ const { sendGoodbyeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); // [START onDeleteTrigger] -exports.sendByeEmail = onUserDeleted( +exports.deletedUserFarewell = onUserDeleted( { secrets: [emailApiKey] }, async (event) => { // [END onDeleteTrigger] diff --git a/Node/quickstarts/email-users/functions/index.js b/Node/quickstarts/email-users/functions/index.js index 01662d9685..00661cec42 100644 --- a/Node/quickstarts/email-users/functions/index.js +++ b/Node/quickstarts/email-users/functions/index.js @@ -15,14 +15,14 @@ */ "use strict"; -const { sendWelcomeEmail } = require("./sendWelcomeEmail"); -const { sendByeEmail } = require("./sendByeEmail"); +const { newUserWelcome } = require("./newUserWelcome"); +const { deletedUserFarewell } = require("./deletedUserFarewell"); const { sendWelcomeEmailToTenant, sendWelcomeEmailNoTenant, } = require("./tenants"); -exports.sendWelcomeEmail = sendWelcomeEmail; -exports.sendByeEmail = sendByeEmail; +exports.newUserWelcome = newUserWelcome; +exports.deletedUserFarewell = deletedUserFarewell; exports.sendWelcomeEmailToTenant = sendWelcomeEmailToTenant; exports.sendWelcomeEmailNoTenant = sendWelcomeEmailNoTenant; diff --git a/Node/quickstarts/email-users/functions/sendWelcomeEmail.js b/Node/quickstarts/email-users/functions/newUserWelcome.js similarity index 96% rename from Node/quickstarts/email-users/functions/sendWelcomeEmail.js rename to Node/quickstarts/email-users/functions/newUserWelcome.js index 90f3a56df7..8da8f5fb3f 100644 --- a/Node/quickstarts/email-users/functions/sendWelcomeEmail.js +++ b/Node/quickstarts/email-users/functions/newUserWelcome.js @@ -24,7 +24,7 @@ const { sendWelcomeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); // [START onCreateTrigger] -exports.sendWelcomeEmail = onUserCreated( +exports.newUserWelcome = onUserCreated( { secrets: [emailApiKey] }, async (event) => { // [END onCreateTrigger] diff --git a/Node/quickstarts/email-users/functions/utils/myEmailService.js b/Node/quickstarts/email-users/functions/utils/myEmailService.js index c2598ff596..849f905ad6 100644 --- a/Node/quickstarts/email-users/functions/utils/myEmailService.js +++ b/Node/quickstarts/email-users/functions/utils/myEmailService.js @@ -32,7 +32,7 @@ const emailApiKey = defineSecret("EMAIL_API_KEY"); */ async function sendEmail({ to, subject, text }) { if (!to) { - throw new Error('Missing email address.'); + throw new Error("Missing email address."); } const resend = new Resend(emailApiKey.value()); await resend.emails.send({ From 2f32669b92b1cda35e2c14252d37dd5f213a3db9 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Fri, 11 Sep 2026 18:30:42 +0000 Subject: [PATCH 07/12] focus tenant samples more on tenant-specific stuff --- Node/quickstarts/email-users/functions/tenants.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Node/quickstarts/email-users/functions/tenants.js b/Node/quickstarts/email-users/functions/tenants.js index 6f349c2067..8234838270 100644 --- a/Node/quickstarts/email-users/functions/tenants.js +++ b/Node/quickstarts/email-users/functions/tenants.js @@ -18,7 +18,6 @@ // [START multitenancy] const { onUserCreated, IS_NOT_TENANT } = require("firebase-functions/identity"); const { defineSecret } = require("firebase-functions/params"); -const { logger } = require("firebase-functions"); const { sendWelcomeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); @@ -34,13 +33,8 @@ exports.sendWelcomeEmailToTenant = onUserCreated( }, async (event) => { const { uid, email, displayName } = event.data; - if (!email) { - logger.log(`User ${uid} does not have an email address.`); - return; - } - // Customize the email for this tenant - await sendWelcomeEmail(email, displayName, "my-tenant-id"); + await sendWelcomeEmail(email, displayName, event.tenantId); }, ); @@ -54,11 +48,7 @@ exports.sendWelcomeEmailNoTenant = onUserCreated( tenantId: IS_NOT_TENANT, }, async (event) => { - const { uid, email, displayName } = event.data; - if (!email) { - logger.log(`user ${uid} does not have an email address.`); - return; - } + const { email, displayName } = event.data; // Send a generic welcome email await sendWelcomeEmail(email, displayName); From 3b3162a0bb4375d8982ff90dfbacf013622cbdd4 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Fri, 11 Sep 2026 18:45:22 +0000 Subject: [PATCH 08/12] add more snippet tags --- .../email-users/functions/deletedUserFarewell.js | 2 ++ .../email-users/functions/newUserWelcome.js | 2 ++ Node/quickstarts/email-users/functions/tenants.js | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/Node/quickstarts/email-users/functions/deletedUserFarewell.js b/Node/quickstarts/email-users/functions/deletedUserFarewell.js index c86f62a038..259d940761 100644 --- a/Node/quickstarts/email-users/functions/deletedUserFarewell.js +++ b/Node/quickstarts/email-users/functions/deletedUserFarewell.js @@ -16,7 +16,9 @@ "use strict"; // [START sendByeEmail] +// [START onDeleteImport] const { onUserDeleted } = require("firebase-functions/identity"); +// [END onDeleteImport] const { defineSecret } = require("firebase-functions/params"); const { logger } = require("firebase-functions"); const { sendGoodbyeEmail } = require("./utils/myEmailService"); diff --git a/Node/quickstarts/email-users/functions/newUserWelcome.js b/Node/quickstarts/email-users/functions/newUserWelcome.js index 8da8f5fb3f..7bdd22fc02 100644 --- a/Node/quickstarts/email-users/functions/newUserWelcome.js +++ b/Node/quickstarts/email-users/functions/newUserWelcome.js @@ -16,7 +16,9 @@ "use strict"; // [START sendWelcomeEmail] +// [START onCreateImport] const { onUserCreated } = require("firebase-functions/identity"); +// [END onCreateImport] const { defineSecret } = require("firebase-functions/params"); const { logger } = require("firebase-functions"); const { sendWelcomeEmail } = require("./utils/myEmailService"); diff --git a/Node/quickstarts/email-users/functions/tenants.js b/Node/quickstarts/email-users/functions/tenants.js index 8234838270..4e0a153633 100644 --- a/Node/quickstarts/email-users/functions/tenants.js +++ b/Node/quickstarts/email-users/functions/tenants.js @@ -16,15 +16,19 @@ "use strict"; // [START multitenancy] +// [START multitenancyImports] const { onUserCreated, IS_NOT_TENANT } = require("firebase-functions/identity"); +// [END multitenancyImports] const { defineSecret } = require("firebase-functions/params"); const { sendWelcomeEmail } = require("./utils/myEmailService"); const emailApiKey = defineSecret("EMAIL_API_KEY"); +// [START sendWelcomeEmailToTenant] /** * Sends a welcome email scoped to a specific tenant in Identity Platform. */ +// [START sendWelcomeEmailToTenantTrigger] exports.sendWelcomeEmailToTenant = onUserCreated( { secrets: [emailApiKey], @@ -32,15 +36,19 @@ exports.sendWelcomeEmailToTenant = onUserCreated( tenantId: "my-tenant-id", }, async (event) => { + // [END sendWelcomeEmailToTenantTrigger] const { uid, email, displayName } = event.data; // Customize the email for this tenant await sendWelcomeEmail(email, displayName, event.tenantId); }, ); +// [END sendWelcomeEmailToTenant] +// [START sendWelcomeEmailNoTenant] /** * Sends a welcome email only to users not associated with any tenant. */ +// [START sendWelcomeEmailNoTenantTrigger] exports.sendWelcomeEmailNoTenant = onUserCreated( { secrets: [emailApiKey], @@ -48,10 +56,12 @@ exports.sendWelcomeEmailNoTenant = onUserCreated( tenantId: IS_NOT_TENANT, }, async (event) => { + // [END sendWelcomeEmailNoTenantTrigger] const { email, displayName } = event.data; // Send a generic welcome email await sendWelcomeEmail(email, displayName); }, ); +// [END sendWelcomeEmailNoTenant] // [END multitenancy] From bd527f9a52f6f07ba6ae47441fa9f4bb84941196 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Fri, 11 Sep 2026 18:47:32 +0000 Subject: [PATCH 09/12] typo --- Node/quickstarts/email-users/functions/utils/myEmailService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Node/quickstarts/email-users/functions/utils/myEmailService.js b/Node/quickstarts/email-users/functions/utils/myEmailService.js index 849f905ad6..ed02756de2 100644 --- a/Node/quickstarts/email-users/functions/utils/myEmailService.js +++ b/Node/quickstarts/email-users/functions/utils/myEmailService.js @@ -80,7 +80,7 @@ async function sendGoodbyeEmail(email, displayName, tenantId) { await sendEmail({ to: email, subject: "Bye!", - text: `${greeting}${name}!, We confirm that we have deleted your My Firebase demo account.`, + text: `${greeting}${name}! We confirm that we have deleted your My Firebase demo account.`, }); logger.log("Account deletion confirmation email sent to:", email); } From b6a29865cedd94e1f14ffcbcb009c163ba90dfb6 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:02:06 +0000 Subject: [PATCH 10/12] use a version with auth events --- Node/quickstarts/email-users/functions/package.json | 2 +- Node/quickstarts/email-users/functions/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Node/quickstarts/email-users/functions/package.json b/Node/quickstarts/email-users/functions/package.json index 30b4aaae42..ee6b1e4dbb 100644 --- a/Node/quickstarts/email-users/functions/package.json +++ b/Node/quickstarts/email-users/functions/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "firebase-admin": "^14.2.0", - "firebase-functions": "^7.3.2", + "firebase-functions": "7.3.3-rc.3", "resend": "^4.0.0" }, "devDependencies": { diff --git a/Node/quickstarts/email-users/functions/pnpm-lock.yaml b/Node/quickstarts/email-users/functions/pnpm-lock.yaml index 01bcb95e62..8362eb3c94 100644 --- a/Node/quickstarts/email-users/functions/pnpm-lock.yaml +++ b/Node/quickstarts/email-users/functions/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^14.2.0 version: 14.2.0(supports-color@7.2.0) firebase-functions: - specifier: ^7.3.2 - version: 7.3.2(firebase-admin@14.2.0(supports-color@7.2.0))(supports-color@7.2.0) + specifier: 7.3.3-rc.3 + version: 7.3.3-rc.3(firebase-admin@14.2.0(supports-color@7.2.0))(supports-color@7.2.0) resend: specifier: ^4.0.0 version: 4.8.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -599,8 +599,8 @@ packages: resolution: {integrity: sha512-zfs5PdccEgjX479bbMmz95Rqc7ttQ4LV3qkGFlPiqOaOu/suBZc3fG52Qzn2hQjiSHkBM4NP2AZV5r2NvAt0TQ==} engines: {node: '>=22'} - firebase-functions@7.3.2: - resolution: {integrity: sha512-DTa5CtCNxFE1gPjkjlhl5uh8AcNWXLKORAMRenfkecP8zGstbje+r2TaNh4Ehdxp0qTPvTUe/sS23VESW32Gbg==} + firebase-functions@7.3.3-rc.3: + resolution: {integrity: sha512-ZSk85ExZSIgRaKZiTD2Z8Q4dU6KIeIVfwYT75PWC4KlaEgnoAyH6l4fmbP2IeAV3K8WQpLP9A5uTJIKMzeqL8A==} engines: {node: '>=18.0.0'} hasBin: true peerDependencies: @@ -2117,7 +2117,7 @@ snapshots: - encoding - supports-color - firebase-functions@7.3.2(firebase-admin@14.2.0(supports-color@7.2.0))(supports-color@7.2.0): + firebase-functions@7.3.3-rc.3(firebase-admin@14.2.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@types/cors': 2.8.19 '@types/express': 5.0.6 From bf4cfb1a0b71e0897221f8eebc894866315e735a Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:02:26 +0000 Subject: [PATCH 11/12] log before email is sent so it's clear it is working even if you use an invalid api key --- Node/quickstarts/email-users/functions/utils/myEmailService.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Node/quickstarts/email-users/functions/utils/myEmailService.js b/Node/quickstarts/email-users/functions/utils/myEmailService.js index ed02756de2..d6a59c7e09 100644 --- a/Node/quickstarts/email-users/functions/utils/myEmailService.js +++ b/Node/quickstarts/email-users/functions/utils/myEmailService.js @@ -31,6 +31,7 @@ const emailApiKey = defineSecret("EMAIL_API_KEY"); * @return {Promise} */ async function sendEmail({ to, subject, text }) { + logger.log({ to, subject, text }); if (!to) { throw new Error("Missing email address."); } From f54ae87b54168a30f989dc58f9e6f125798048a4 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:30:38 +0000 Subject: [PATCH 12/12] Revert accidental changes to Node/delete-unused-accounts-cron/functions/index.js --- .../functions/index.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Node/delete-unused-accounts-cron/functions/index.js b/Node/delete-unused-accounts-cron/functions/index.js index e808df7f8a..837735a065 100644 --- a/Node/delete-unused-accounts-cron/functions/index.js +++ b/Node/delete-unused-accounts-cron/functions/index.js @@ -22,9 +22,8 @@ const {onSchedule} = require("firebase-functions/scheduler"); const {logger} = require("firebase-functions"); // The Firebase Admin SDK to delete inactive users. -const {initializeApp} = require("firebase-admin/app"); -const {getAuth} = require("firebase-admin/auth"); -initializeApp(); +const admin = require("firebase-admin"); +admin.initializeApp(); // The es6-promise-pool to limit the concurrency of promises. const PromisePool = require("es6-promise-pool").default; @@ -53,7 +52,7 @@ exports.accountcleanup = onSchedule("every day 00:00", async (event) => { // [START deleteInactiveUser] /** * Deletes one inactive user from the list. - * @param {import("firebase-admin/auth").UserRecord[]} inactiveUsers + * @param {admin.auth.UserRecord[]} inactiveUsers * @return {null | Promise} */ function deleteInactiveUser(inactiveUsers) { @@ -61,7 +60,7 @@ function deleteInactiveUser(inactiveUsers) { const userToDelete = inactiveUsers.pop(); // Delete the inactive user. - return getAuth().deleteUser(userToDelete.uid).then(() => { + return admin.auth().deleteUser(userToDelete.uid).then(() => { return logger.log( "Deleted user account", userToDelete.uid, @@ -85,12 +84,12 @@ function deleteInactiveUser(inactiveUsers) { // Returns the list of all inactive users. /** * - * @param {import("firebase-admin/auth").UserRecord[]} [users] the current list of inactive users + * @param {admin.auth.UserRecord[]} [users] the current list of inactive users * @param {string} [nextPageToken] - * @return {Promise} + * @return {Promise} */ async function getInactiveUsers(users = [], nextPageToken) { - const result = await getAuth().listUsers(1000, nextPageToken); + const result = await admin.auth().listUsers(1000, nextPageToken); // Find users that have not signed in in the last 30 days. const inactiveUsers = result.users.filter( (user) =>