diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 00000000..46b92aeb --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "pinball-dev" + } +} diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index a805cebc..5bef442d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,6 +11,6 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: flutter_channel: stable - flutter_version: 2.10.0 + flutter_version: 2.10.5 coverage_excludes: "lib/gen/*.dart" test_optimization: false diff --git a/firebase.json b/firebase.json index 1338aeba..c0153ecc 100644 --- a/firebase.json +++ b/firebase.json @@ -5,7 +5,11 @@ "hosting": { "public": "build/web", "site": "ashehwkdkdjruejdnensjsjdne", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], "headers": [ { "source": "**/*.@(jpg|jpeg|gif|png)", diff --git a/firestore.rules b/firestore.rules index fbff78f0..ba0521d0 100644 --- a/firestore.rules +++ b/firestore.rules @@ -17,7 +17,7 @@ service cloud.firestore { } // Leaderboard can be read if it doesn't contain any prohibited initials - allow read: if !prohibited(resource.data.playerInitials); + allow read: if isAuthedUser(request.auth); // A leaderboard entry can be created if the user is authenticated, // it's 3 characters long, and not a prohibited combination. diff --git a/functions/.gitignore b/functions/.gitignore new file mode 100644 index 00000000..40b878db --- /dev/null +++ b/functions/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/functions/index.js b/functions/index.js new file mode 100644 index 00000000..1f676c41 --- /dev/null +++ b/functions/index.js @@ -0,0 +1,28 @@ +const functions = require("firebase-functions"); +const admin = require("firebase-admin"); +admin.initializeApp(); + +const db = admin.firestore(); + +exports.timedLeaderboardCleanup = functions.firestore + .document("leaderboard/{leaderboardEntry}") + .onCreate(async (_, __) => { + functions.logger.info( + "Document created, getting all leaderboard documents" + ); + const snapshot = await db + .collection("leaderboard") + .orderBy("score", "desc") + .offset(10) + .get(); + + functions.logger.info( + `Preparing to delete ${snapshot.docs.length} documents.` + ); + try { + await Promise.all(snapshot.docs.map((doc) => doc.ref.delete())); + functions.logger.info("Success"); + } catch (error) { + functions.logger.error(`Failed to delete documents ${error}`); + } + }); diff --git a/functions/package-lock.json b/functions/package-lock.json new file mode 100644 index 00000000..9f2f1224 --- /dev/null +++ b/functions/package-lock.json @@ -0,0 +1,4357 @@ +{ + "name": "functions", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "functions", + "dependencies": { + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" + }, + "devDependencies": { + "firebase-functions-test": "^0.2.0" + }, + "engines": { + "node": "16" + } + }, + "node_modules/@firebase/app": { + "version": "0.7.23", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.23.tgz", + "integrity": "sha512-l4lWct3DrCGwwUtNzoKmOVGkWrbULGwPvV63888xuPppScd0VN/W7iOWJIOye6kEPuwrazTWGa+YQZYTaQK49g==", + "peer": true, + "dependencies": { + "@firebase/component": "0.5.14", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.6.0", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.24.tgz", + "integrity": "sha512-qDDo9vvFzpVoE4EpgzSMYOJ7wsy6cwh4lfz2/RqeNpmJniybcDNaxEVO52ejCNqIQ2yVo6yBbK8D0b5FrO8P2w==", + "peer": true, + "dependencies": { + "@firebase/app": "0.7.23", + "@firebase/component": "0.5.14", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.6.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.14.tgz", + "integrity": "sha512-ct2p1MTMV5P/nGIlkC3XjAVwHwjsIZaeo8JVyDAkJCNTROu5mYX3FBK16hjIUIIVJDpgnnzFh9nP74gciL4WrA==", + "peer": true, + "dependencies": { + "@firebase/util": "1.6.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.12.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.8.tgz", + "integrity": "sha512-JBQVfFLzfhxlQbl4OU6ov9fdsddkytBQdtSSR49cz48homj38ccltAhK6seum+BI7f28cV2LFHF9672lcN+qxA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.8.tgz", + "integrity": "sha512-dhXr5CSieBuKNdU96HgeewMQCT9EgOIkfF1GNy+iRrdl7BWLxmlKuvLfK319rmIytSs/vnCzcD9uqyxTeU/A3A==", + "dependencies": { + "@firebase/component": "0.5.13", + "@firebase/database": "0.12.8", + "@firebase/database-types": "0.9.7", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/component": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "dependencies": { + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.7.tgz", + "integrity": "sha512-EFhgL89Fz6DY3kkB8TzdHvdu8XaqqvzcF2DLVOXEnQ3Ms7L755p5EO42LfxXoJqb9jKFvgLpFmKicyJG25WFWw==", + "dependencies": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.5.2" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.8.tgz", + "integrity": "sha512-bI7bwF5xc0nPi6Oa3JVt6JJdfhVAnEpCwgfTNILR4lYDPtxdxlRXhZzQ5lfqlCj7PR+drKh9RvMu6C24N1q04w==", + "dependencies": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.6.0" + } + }, + "node_modules/@firebase/database/node_modules/@firebase/component": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "dependencies": { + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database/node_modules/@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.0.tgz", + "integrity": "sha512-6+hhqb4Zzjoo12xofTDHPkgW3FnN4ydBsjd5X2KuQI268DR3W3Ld64W/gkKPZrKRgUxeNeb+pykfP3qRe7q+vA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/storage": { + "version": "5.19.4", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.19.4.tgz", + "integrity": "sha512-Jz7ugcPHhsEmMVvIxM9uoBsdEbKIYwDkh3u07tifsIymEWs47F4/D6+/Tv/W7kLhznqjyOjVJ/0frtBeIC0lJA==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "configstore": "^5.0.0", + "date-and-time": "^2.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "get-stream": "^6.0.0", + "google-auth-library": "^7.14.1", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "retry-request": "^4.2.2", + "snakeize": "^0.1.0", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", + "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "optional": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "optional": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + }, + "node_modules/@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/node": { + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/date-and-time": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.3.1.tgz", + "integrity": "sha512-OaIRmSJXifwEN21rMVVDs0Kz8uhJ3wWPYd86atkRiqN54liaMQYEbbrgjZQea75YXOBWL4ZFb3rG/waenw1TEg==", + "optional": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dicer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz", + "integrity": "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "optional": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "optional": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "optional": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", + "optional": true + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/firebase-admin": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.2.0.tgz", + "integrity": "sha512-6ehn5J9UEFgi4+naqYvozmGpnZae3cJLdwSkSsDc8/Y0eTBjVMFdf9N2ft7N81UNHA0N5DknOyXhlsdAdyBLCA==", + "dependencies": { + "@firebase/database-compat": "^0.1.8", + "@firebase/database-types": "^0.9.7", + "@types/node": ">=12.12.47", + "dicer": "^0.3.0", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", + "node-forge": "^1.3.1", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3" + } + }, + "node_modules/firebase-functions": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.21.0.tgz", + "integrity": "sha512-Xl0EFV05+RSB9hJHlo12LguoRqAmpSXmBnpI0MANeNj07dGW0QCWPHtaAewxYEzRS3RfSCL8WH12rjvo0PPKGw==", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "lodash": "^4.17.14", + "node-fetch": "^2.6.7" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "peerDependencies": { + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/firebase-functions-test": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.2.3.tgz", + "integrity": "sha512-zYX0QTm53wCazuej7O0xqbHl90r/v1PTXt/hwa0jo1YF8nDM+iBKnLDlkIoW66MDd0R6aGg4BvKzTTdJpvigUA==", + "dev": true, + "dependencies": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "firebase-admin": ">=6.0.0", + "firebase-functions": ">=2.0.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "optional": true + }, + "node_modules/gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "optional": true, + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-gax": { + "version": "2.30.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.3.tgz", + "integrity": "sha512-Zsd6hbJBMvAcJS3cYpAsmupvfsxygFR2meUZJcGeR7iUqYHCR/1Hf2aQNB9srrlXQMm91pNiUvW0Kz6Qld8QkA==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "~1.6.0", + "@grpc/proto-loader": "0.6.9", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.14.0", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^0.1.8", + "protobufjs": "6.11.2", + "retry-request": "^4.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "optional": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "optional": true, + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", + "peer": true + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "optional": true + }, + "node_modules/jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "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.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.1.tgz", + "integrity": "sha512-NdtZuf1pvlnWgbCU2c9oRFdi+u8uWpwL6EMiordpasrNFNrGrQGNs7WQ9UiMT0/RG7vt4ElwyObNy04pIbsGCA==", + "dependencies": { + "@types/express": "^4.17.13", + "debug": "^4.3.4", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=10 < 13 || >=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/proto3-json-serializer": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.8.tgz", + "integrity": "sha512-ACilkB6s1U1gWnl5jtICpnDai4VCxmI9GFxuEaYdxtDG2oVI3sVFIUsvUZcQbJgtPM6p+zqKbjTKQZp6Y4FpQw==", + "optional": true, + "dependencies": { + "protobufjs": "^6.11.2" + } + }, + "node_modules/protobufjs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/retry-request/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/retry-request/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "optional": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "optional": true + }, + "node_modules/teeny-request": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@firebase/app": { + "version": "0.7.23", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.23.tgz", + "integrity": "sha512-l4lWct3DrCGwwUtNzoKmOVGkWrbULGwPvV63888xuPppScd0VN/W7iOWJIOye6kEPuwrazTWGa+YQZYTaQK49g==", + "peer": true, + "requires": { + "@firebase/component": "0.5.14", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.6.0", + "idb": "7.0.1", + "tslib": "^2.1.0" + } + }, + "@firebase/app-compat": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.24.tgz", + "integrity": "sha512-qDDo9vvFzpVoE4EpgzSMYOJ7wsy6cwh4lfz2/RqeNpmJniybcDNaxEVO52ejCNqIQ2yVo6yBbK8D0b5FrO8P2w==", + "peer": true, + "requires": { + "@firebase/app": "0.7.23", + "@firebase/component": "0.5.14", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.6.0", + "tslib": "^2.1.0" + } + }, + "@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + }, + "@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "requires": {} + }, + "@firebase/component": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.14.tgz", + "integrity": "sha512-ct2p1MTMV5P/nGIlkC3XjAVwHwjsIZaeo8JVyDAkJCNTROu5mYX3FBK16hjIUIIVJDpgnnzFh9nP74gciL4WrA==", + "peer": true, + "requires": { + "@firebase/util": "1.6.0", + "tslib": "^2.1.0" + } + }, + "@firebase/database": { + "version": "0.12.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.8.tgz", + "integrity": "sha512-JBQVfFLzfhxlQbl4OU6ov9fdsddkytBQdtSSR49cz48homj38ccltAhK6seum+BI7f28cV2LFHF9672lcN+qxA==", + "requires": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/component": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "requires": { + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "requires": { + "tslib": "^2.1.0" + } + } + } + }, + "@firebase/database-compat": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.8.tgz", + "integrity": "sha512-dhXr5CSieBuKNdU96HgeewMQCT9EgOIkfF1GNy+iRrdl7BWLxmlKuvLfK319rmIytSs/vnCzcD9uqyxTeU/A3A==", + "requires": { + "@firebase/component": "0.5.13", + "@firebase/database": "0.12.8", + "@firebase/database-types": "0.9.7", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/component": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "requires": { + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "@firebase/database-types": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.7.tgz", + "integrity": "sha512-EFhgL89Fz6DY3kkB8TzdHvdu8XaqqvzcF2DLVOXEnQ3Ms7L755p5EO42LfxXoJqb9jKFvgLpFmKicyJG25WFWw==", + "requires": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.5.2" + } + }, + "@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "requires": { + "tslib": "^2.1.0" + } + } + } + }, + "@firebase/database-types": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.8.tgz", + "integrity": "sha512-bI7bwF5xc0nPi6Oa3JVt6JJdfhVAnEpCwgfTNILR4lYDPtxdxlRXhZzQ5lfqlCj7PR+drKh9RvMu6C24N1q04w==", + "requires": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.6.0" + } + }, + "@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.0.tgz", + "integrity": "sha512-6+hhqb4Zzjoo12xofTDHPkgW3FnN4ydBsjd5X2KuQI268DR3W3Ld64W/gkKPZrKRgUxeNeb+pykfP3qRe7q+vA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@google-cloud/firestore": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "optional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" + } + }, + "@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "optional": true + }, + "@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "optional": true + }, + "@google-cloud/storage": { + "version": "5.19.4", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.19.4.tgz", + "integrity": "sha512-Jz7ugcPHhsEmMVvIxM9uoBsdEbKIYwDkh3u07tifsIymEWs47F4/D6+/Tv/W7kLhznqjyOjVJ/0frtBeIC0lJA==", + "optional": true, + "requires": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "configstore": "^5.0.0", + "date-and-time": "^2.0.0", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "get-stream": "^6.0.0", + "google-auth-library": "^7.14.1", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "retry-request": "^4.2.2", + "snakeize": "^0.1.0", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "xdg-basedir": "^4.0.0" + } + }, + "@grpc/grpc-js": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", + "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "optional": true, + "requires": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + } + }, + "@grpc/proto-loader": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", + "optional": true, + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" + } + }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "optional": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "optional": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "optional": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "optional": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "optional": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "optional": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "optional": true + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + }, + "@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "@types/node": { + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==" + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true + }, + "async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "requires": { + "retry": "0.13.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "optional": true + }, + "bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "optional": true + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true + }, + "date-and-time": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.3.1.tgz", + "integrity": "sha512-OaIRmSJXifwEN21rMVVDs0Kz8uhJ3wWPYd86atkRiqN54liaMQYEbbrgjZQea75YXOBWL4ZFb3rG/waenw1TEg==", + "optional": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "dicer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz", + "integrity": "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "optional": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "requires": { + "once": "^1.4.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "optional": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "optional": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true + }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "optional": true + }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", + "optional": true + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "firebase-admin": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.2.0.tgz", + "integrity": "sha512-6ehn5J9UEFgi4+naqYvozmGpnZae3cJLdwSkSsDc8/Y0eTBjVMFdf9N2ft7N81UNHA0N5DknOyXhlsdAdyBLCA==", + "requires": { + "@firebase/database-compat": "^0.1.8", + "@firebase/database-types": "^0.9.7", + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3", + "@types/node": ">=12.12.47", + "dicer": "^0.3.0", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", + "node-forge": "^1.3.1", + "uuid": "^8.3.2" + } + }, + "firebase-functions": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.21.0.tgz", + "integrity": "sha512-Xl0EFV05+RSB9hJHlo12LguoRqAmpSXmBnpI0MANeNj07dGW0QCWPHtaAewxYEzRS3RfSCL8WH12rjvo0PPKGw==", + "requires": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "lodash": "^4.17.14", + "node-fetch": "^2.6.7" + } + }, + "firebase-functions-test": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.2.3.tgz", + "integrity": "sha512-zYX0QTm53wCazuej7O0xqbHl90r/v1PTXt/hwa0jo1YF8nDM+iBKnLDlkIoW66MDd0R6aGg4BvKzTTdJpvigUA==", + "dev": true, + "requires": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "optional": true + }, + "gaxios": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + } + }, + "gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "optional": true + }, + "google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-gax": { + "version": "2.30.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.3.tgz", + "integrity": "sha512-Zsd6hbJBMvAcJS3cYpAsmupvfsxygFR2meUZJcGeR7iUqYHCR/1Hf2aQNB9srrlXQMm91pNiUvW0Kz6Qld8QkA==", + "optional": true, + "requires": { + "@grpc/grpc-js": "~1.6.0", + "@grpc/proto-loader": "0.6.9", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.14.0", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^0.1.8", + "protobufjs": "6.11.2", + "retry-request": "^4.0.0" + } + }, + "google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "optional": true, + "requires": { + "node-forge": "^1.3.1" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, + "gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==" + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", + "peer": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "optional": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "optional": true + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "optional": true + }, + "jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "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.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jwks-rsa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.1.tgz", + "integrity": "sha512-NdtZuf1pvlnWgbCU2c9oRFdi+u8uWpwL6EMiordpasrNFNrGrQGNs7WQ9UiMT0/RG7vt4ElwyObNy04pIbsGCA==", + "requires": { + "@types/express": "^4.17.13", + "debug": "^4.3.4", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "dependencies": { + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "optional": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true + }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "optional": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proto3-json-serializer": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.8.tgz", + "integrity": "sha512-ACilkB6s1U1gWnl5jtICpnDai4VCxmI9GFxuEaYdxtDG2oVI3sVFIUsvUZcQbJgtPM6p+zqKbjTKQZp6Y4FpQw==", + "optional": true, + "requires": { + "protobufjs": "^6.11.2" + } + }, + "protobufjs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "optional": true + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true + }, + "retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "optional": true, + "requires": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "optional": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "optional": true + }, + "teeny-request": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "optional": true, + "requires": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "optional": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "optional": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "optional": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "optional": true + } + } +} diff --git a/functions/package.json b/functions/package.json new file mode 100644 index 00000000..148afc6a --- /dev/null +++ b/functions/package.json @@ -0,0 +1,23 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": { + "serve": "firebase emulators:start --only functions", + "shell": "firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "16" + }, + "main": "index.js", + "dependencies": { + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" + }, + "devDependencies": { + "firebase-functions-test": "^0.2.0" + }, + "private": true +} diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index a44d2e33..ae3094e1 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -39,6 +39,7 @@ class App extends StatelessWidget { providers: [ BlocProvider(create: (_) => CharacterThemeCubit()), BlocProvider(create: (_) => StartGameBloc()), + BlocProvider(create: (_) => GameBloc()), ], child: MaterialApp( title: 'I/O Pinball', diff --git a/lib/footer/footer.dart b/lib/footer/footer.dart deleted file mode 100644 index df3dbd2f..00000000 --- a/lib/footer/footer.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:pinball/l10n/l10n.dart'; -import 'package:pinball_ui/pinball_ui.dart'; - -/// {@template footer} -/// Footer widget with links to the main tech stack. -/// {@endtemplate} -class Footer extends StatelessWidget { - /// {@macro footer} - const Footer({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(50, 0, 50, 32), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ - _MadeWithFlutterAndFirebase(), - _GoogleIO(), - ], - ), - ); - } -} - -class _GoogleIO extends StatelessWidget { - const _GoogleIO({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final theme = Theme.of(context); - return Text( - l10n.footerGoogleIOText, - style: theme.textTheme.bodyText1!.copyWith(color: PinballColors.white), - ); - } -} - -class _MadeWithFlutterAndFirebase extends StatelessWidget { - const _MadeWithFlutterAndFirebase({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final theme = Theme.of(context); - return RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: l10n.footerMadeWithText, - style: theme.textTheme.bodyText1!.copyWith(color: PinballColors.white), - children: [ - TextSpan( - text: l10n.footerFlutterLinkText, - recognizer: TapGestureRecognizer() - ..onTap = () => openLink('https://flutter.dev'), - style: const TextStyle( - decoration: TextDecoration.underline, - ), - ), - const TextSpan(text: ' & '), - TextSpan( - text: l10n.footerFirebaseLinkText, - recognizer: TapGestureRecognizer() - ..onTap = () => openLink('https://firebase.google.com'), - style: const TextStyle( - decoration: TextDecoration.underline, - ), - ), - ], - ), - ); - } -} diff --git a/lib/game/behaviors/ball_spawning_behavior.dart b/lib/game/behaviors/ball_spawning_behavior.dart index c074fe52..8995c16b 100644 --- a/lib/game/behaviors/ball_spawning_behavior.dart +++ b/lib/game/behaviors/ball_spawning_behavior.dart @@ -1,9 +1,9 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Spawns a new [Ball] into the game when all balls are lost and still /// [GameStatus.playing]. @@ -23,12 +23,16 @@ class BallSpawningBehavior extends Component void onNewState(GameState state) { final plunger = gameRef.descendants().whereType().single; final canvas = gameRef.descendants().whereType().single; - final characterTheme = readProvider(); - final ball = ControlledBall.launch(characterTheme: characterTheme) + final characterTheme = readBloc() + .state + .characterTheme; + final ball = Ball(assetPath: characterTheme.ball.keyName) ..initialPosition = Vector2( plunger.body.position.x, plunger.body.position.y - Ball.size.y, - ); + ) + ..layer = Layer.launcher + ..zIndex = ZIndexes.ballOnLaunchRamp; canvas.add(ball); } diff --git a/lib/game/behaviors/ball_theming_behavior.dart b/lib/game/behaviors/ball_theming_behavior.dart new file mode 100644 index 00000000..5e12a720 --- /dev/null +++ b/lib/game/behaviors/ball_theming_behavior.dart @@ -0,0 +1,20 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// Updates the launch [Ball] to reflect character selections. +class BallThemingBehavior extends Component + with + FlameBlocListenable, + HasGameRef { + @override + void onNewState(CharacterThemeState state) { + gameRef + .descendants() + .whereType() + .single + .bloc + .onThemeChanged(state.characterTheme); + } +} diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index 44cce1df..301bc61e 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -1,4 +1,6 @@ export 'ball_spawning_behavior.dart'; +export 'ball_theming_behavior.dart'; +export 'bonus_noise_behavior.dart'; export 'bumper_noise_behavior.dart'; export 'camera_focusing_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/behaviors/bonus_noise_behavior.dart b/lib/game/behaviors/bonus_noise_behavior.dart new file mode 100644 index 00000000..70c8ad3e --- /dev/null +++ b/lib/game/behaviors/bonus_noise_behavior.dart @@ -0,0 +1,38 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Behavior that handles playing a bonus sound effect +class BonusNoiseBehavior extends Component { + @override + Future onLoad() async { + await add( + FlameBlocListener( + listenWhen: (previous, current) { + return previous.bonusHistory.length != current.bonusHistory.length; + }, + onNewState: (state) { + final bonus = state.bonusHistory.last; + final audioPlayer = readProvider(); + + switch (bonus) { + case GameBonus.googleWord: + audioPlayer.play(PinballAudio.google); + break; + case GameBonus.sparkyTurboCharge: + audioPlayer.play(PinballAudio.sparky); + break; + case GameBonus.dinoChomp: + break; + case GameBonus.androidSpaceship: + break; + case GameBonus.dashNest: + break; + } + }, + ), + ); + } +} diff --git a/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart b/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart index da181f9e..cbb6e516 100644 --- a/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart +++ b/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart @@ -11,10 +11,6 @@ class AndroidSpaceshipBonusBehavior extends Component void onMount() { super.onMount(); final androidSpaceship = parent.firstChild()!; - - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 androidSpaceship.bloc.stream.listen((state) { final listenWhen = state == AndroidSpaceshipState.withBonus; if (!listenWhen) return; diff --git a/lib/game/components/backbox/displays/initials_input_display.dart b/lib/game/components/backbox/displays/initials_input_display.dart index 244a3e5b..68c58f43 100644 --- a/lib/game/components/backbox/displays/initials_input_display.dart +++ b/lib/game/components/backbox/displays/initials_input_display.dart @@ -32,7 +32,6 @@ final _subtitleTextPaint = TextPaint( /// {@template initials_input_display} /// Display that handles the user input on the game over view. /// {@endtemplate} -// TODO(allisonryan0002): add mobile input buttons. class InitialsInputDisplay extends Component with HasGameRef { /// {@macro initials_input_display} InitialsInputDisplay({ diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index 13bef589..4c6b2822 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -9,7 +9,6 @@ import 'package:pinball_flame/pinball_flame.dart'; /// /// The [BottomGroup] consists of [Flipper]s, [Baseboard]s and [Kicker]s. /// {@endtemplate} -// TODO(allisonryan0002): Consider renaming. class BottomGroup extends Component with ZIndex { /// {@macro bottom_group} BottomGroup() diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index b96b6a65..08dc5cb0 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,7 +1,6 @@ export 'android_acres/android_acres.dart'; export 'backbox/backbox.dart'; export 'bottom_group.dart'; -export 'controlled_ball.dart'; export 'controlled_flipper.dart'; export 'controlled_plunger.dart'; export 'dino_desert/dino_desert.dart'; @@ -12,4 +11,4 @@ export 'google_word/google_word.dart'; export 'launcher.dart'; export 'multiballs/multiballs.dart'; export 'multipliers/multipliers.dart'; -export 'sparky_scorch.dart'; +export 'sparky_scorch/sparky_scorch.dart'; diff --git a/lib/game/components/controlled_ball.dart b/lib/game/components/controlled_ball.dart deleted file mode 100644 index 241465dd..00000000 --- a/lib/game/components/controlled_ball.dart +++ /dev/null @@ -1,66 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'package:flame/components.dart'; -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; - -/// {@template controlled_ball} -/// A [Ball] with a [BallController] attached. -/// -/// When a [Ball] is lost, if there aren't more [Ball]s in play and the game is -/// not over, a new [Ball] will be spawned. -/// {@endtemplate} -class ControlledBall extends Ball with Controls { - /// A [Ball] that launches from the [Plunger]. - ControlledBall.launch({ - required CharacterTheme characterTheme, - }) : super(assetPath: characterTheme.ball.keyName) { - controller = BallController(this); - layer = Layer.launcher; - zIndex = ZIndexes.ballOnLaunchRamp; - } - - /// {@macro controlled_ball} - ControlledBall.bonus({ - required CharacterTheme characterTheme, - }) : super(assetPath: characterTheme.ball.keyName) { - controller = BallController(this); - zIndex = ZIndexes.ballOnBoard; - } - - /// [Ball] used in [DebugPinballGame]. - ControlledBall.debug() : super() { - controller = BallController(this); - zIndex = ZIndexes.ballOnBoard; - } -} - -/// {@template ball_controller} -/// Controller attached to a [Ball] that handles its game related logic. -/// {@endtemplate} -class BallController extends ComponentController - with FlameBlocReader { - /// {@macro ball_controller} - BallController(Ball ball) : super(ball); - - /// Stops the [Ball] inside of the [SparkyComputer] while the turbo charge - /// sequence runs, then boosts the ball out of the computer. - Future turboCharge() async { - bloc.add(const SparkyTurboChargeActivated()); - - component.stop(); - // TODO(alestiago): Refactor this hard coded duration once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1564 - await Future.delayed( - const Duration(milliseconds: 2583), - ); - component.resume(); - await component.add( - BallTurboChargingBehavior(impulse: Vector2(40, 110)), - ); - } -} diff --git a/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart index f1e4f53d..60cd1857 100644 --- a/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart +++ b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart @@ -11,10 +11,6 @@ class ChromeDinoBonusBehavior extends Component void onMount() { super.onMount(); final chromeDino = parent.firstChild()!; - - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 chromeDino.bloc.stream.listen((state) { final listenWhen = state.status == ChromeDinoStatus.chomping; if (!listenWhen) return; diff --git a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart index a4931f90..f37299c7 100644 --- a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart +++ b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart @@ -1,9 +1,9 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Bonus obtained at the [FlutterForest]. /// @@ -25,9 +25,6 @@ class FlutterForestBonusBehavior extends Component final canvas = gameRef.descendants().whereType().single; for (final bumper in bumpers) { - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 bumper.bloc.stream.listen((state) { final activatedAllBumpers = bumpers.every( (bumper) => bumper.bloc.state == DashNestBumperState.active, @@ -41,10 +38,14 @@ class FlutterForestBonusBehavior extends Component if (signpost.bloc.isFullyProgressed()) { bloc.add(const BonusActivated(GameBonus.dashNest)); + final characterTheme = + readBloc() + .state + .characterTheme; canvas.add( - ControlledBall.bonus( - characterTheme: readProvider(), - )..initialPosition = Vector2(29.2, -24.5), + Ball(assetPath: characterTheme.ball.keyName) + ..initialPosition = Vector2(29.2, -24.5) + ..zIndex = ZIndexes.ballOnBoard, ); animatronic.playing = true; signpost.bloc.onProgressed(); diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index 6e11f3d6..81da96d5 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -1,9 +1,9 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; /// Listens to the [GameBloc] and updates the game accordingly. class GameBlocStatusListener extends Component @@ -26,7 +26,9 @@ class GameBlocStatusListener extends Component readProvider().play(PinballAudio.gameOverVoiceOver); gameRef.descendants().whereType().first.requestInitials( score: state.displayScore, - character: readProvider(), + character: readBloc() + .state + .characterTheme, ); break; } diff --git a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart index e49d4537..c1c14ed5 100644 --- a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart +++ b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart @@ -1,7 +1,6 @@ import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball/game/game.dart'; -import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -14,15 +13,11 @@ class GoogleWordBonusBehavior extends Component final googleLetters = parent.children.whereType(); for (final letter in googleLetters) { - // TODO(alestiago): Refactor subscription management once the following is - // merged: - // https://github.com/flame-engine/flame/pull/1538 letter.bloc.stream.listen((_) { final achievedBonus = googleLetters .every((letter) => letter.bloc.state == GoogleLetterState.lit); if (achievedBonus) { - readProvider().play(PinballAudio.google); bloc.add(const BonusActivated(GameBonus.googleWord)); for (final letter in googleLetters) { letter.bloc.onReset(); diff --git a/lib/game/components/sparky_scorch/behaviors/behaviors.dart b/lib/game/components/sparky_scorch/behaviors/behaviors.dart new file mode 100644 index 00000000..3281bb69 --- /dev/null +++ b/lib/game/components/sparky_scorch/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'sparky_computer_bonus_behavior.dart'; diff --git a/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart b/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart new file mode 100644 index 00000000..d1b8898e --- /dev/null +++ b/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart @@ -0,0 +1,24 @@ +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// Adds a [GameBonus.sparkyTurboCharge] when a [Ball] enters the +/// [SparkyComputer]. +class SparkyComputerBonusBehavior extends Component + with ParentIsA, FlameBlocReader { + @override + void onMount() { + super.onMount(); + final sparkyComputer = parent.firstChild()!; + final animatronic = parent.firstChild()!; + sparkyComputer.bloc.stream.listen((state) async { + final listenWhen = state == SparkyComputerState.withBall; + if (!listenWhen) return; + + bloc.add(const BonusActivated(GameBonus.sparkyTurboCharge)); + animatronic.playing = true; + }); + } +} diff --git a/lib/game/components/sparky_scorch.dart b/lib/game/components/sparky_scorch/sparky_scorch.dart similarity index 50% rename from lib/game/components/sparky_scorch.dart rename to lib/game/components/sparky_scorch/sparky_scorch.dart index b820e89d..da624361 100644 --- a/lib/game/components/sparky_scorch.dart +++ b/lib/game/components/sparky_scorch/sparky_scorch.dart @@ -1,9 +1,9 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame/components.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; -import 'package:pinball/game/components/components.dart'; +import 'package:pinball/game/components/sparky_scorch/behaviors/behaviors.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template sparky_scorch} @@ -33,51 +33,20 @@ class SparkyScorch extends Component { BumperNoiseBehavior(), ], )..initialPosition = Vector2(-3.3, -52.55), - SparkyComputerSensor()..initialPosition = Vector2(-13.2, -49.9), SparkyAnimatronic()..position = Vector2(-14, -58.2), - SparkyComputer(), - ], - ); -} - -/// {@template sparky_computer_sensor} -/// Small sensor body used to detect when a ball has entered the -/// [SparkyComputer]. -/// {@endtemplate} -class SparkyComputerSensor extends BodyComponent - with InitialPosition, ContactCallbacks { - /// {@macro sparky_computer_sensor} - SparkyComputerSensor() - : super( - renderBody: false, - children: [ - ScoringContactBehavior(points: Points.twentyThousand), + SparkyComputer( + children: [ + ScoringContactBehavior(points: Points.twoHundredThousand) + ..applyTo(['turbo_charge_sensor']), + ], + ), + SparkyComputerBonusBehavior(), ], ); - @override - Body createBody() { - final shape = PolygonShape() - ..setAsBox( - 1, - 0.1, - Vector2.zero(), - -0.18, - ); - final fixtureDef = FixtureDef(shape, isSensor: true); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - return world.createBody(bodyDef)..createFixture(fixtureDef); - } - - @override - void beginContact(Object other, Contact contact) { - super.beginContact(other, contact); - if (other is! ControlledBall) return; - - other.controller.turboCharge(); - gameRef.firstChild()?.playing = true; - } + /// Creates [SparkyScorch] without any children. + /// + /// This can be used for testing [SparkyScorch]'s behaviors in isolation. + @visibleForTesting + SparkyScorch.test(); } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 458c4198..86eb29c8 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -11,15 +11,15 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart'; class PinballGame extends PinballForge2DGame with HasKeyboardHandlerComponents, MultiTouchTapDetector, HasTappables { PinballGame({ - required CharacterTheme characterTheme, + required CharacterThemeCubit characterThemeBloc, required this.leaderboardRepository, required GameBloc gameBloc, required AppLocalizations l10n, @@ -27,7 +27,7 @@ class PinballGame extends PinballForge2DGame }) : focusNode = FocusNode(), _gameBloc = gameBloc, _player = player, - _characterTheme = characterTheme, + _characterThemeBloc = characterThemeBloc, _l10n = l10n, super( gravity: Vector2(0, 30), @@ -43,7 +43,7 @@ class PinballGame extends PinballForge2DGame final FocusNode focusNode; - final CharacterTheme _characterTheme; + final CharacterThemeCubit _characterThemeBloc; final PinballPlayer _player; @@ -56,19 +56,27 @@ class PinballGame extends PinballForge2DGame @override Future onLoad() async { await add( - FlameBlocProvider.value( - value: _gameBloc, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: _gameBloc, + ), + FlameBlocProvider.value( + value: _characterThemeBloc, + ), + ], children: [ MultiFlameProvider( providers: [ FlameProvider.value(_player), - FlameProvider.value(_characterTheme), FlameProvider.value(leaderboardRepository), FlameProvider.value(_l10n), ], children: [ + BonusNoiseBehavior(), GameBlocStatusListener(), BallSpawningBehavior(), + BallThemingBehavior(), CameraFocusingBehavior(), CanvasComponent( onSpritePainted: (paint) { @@ -160,13 +168,13 @@ class PinballGame extends PinballForge2DGame class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { DebugPinballGame({ - required CharacterTheme characterTheme, + required CharacterThemeCubit characterThemeBloc, required LeaderboardRepository leaderboardRepository, required AppLocalizations l10n, required PinballPlayer player, required GameBloc gameBloc, }) : super( - characterTheme: characterTheme, + characterThemeBloc: characterThemeBloc, player: player, leaderboardRepository: leaderboardRepository, l10n: l10n, @@ -190,8 +198,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { if (info.raw.kind == PointerDeviceKind.mouse) { final canvas = descendants().whereType().single; - final ball = ControlledBall.debug() - ..initialPosition = info.eventPosition.game; + final ball = Ball()..initialPosition = info.eventPosition.game; canvas.add(ball); } } @@ -218,7 +225,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { void _turboChargeBall(Vector2 line) { final canvas = descendants().whereType().single; - final ball = ControlledBall.debug()..initialPosition = lineStart!; + final ball = Ball()..initialPosition = lineStart!; final impulse = line * -1 * 10; ball.add(BallTurboChargingBehavior(impulse: impulse)); canvas.add(ball); @@ -246,7 +253,6 @@ class PreviewLine extends PositionComponent with HasGameRef { } } -// TODO(wolfenrain): investigate this CI failure. class _DebugInformation extends Component with HasGameRef { @override PositionType get positionType => PositionType.widget; @@ -264,7 +270,7 @@ class _DebugInformation extends Component with HasGameRef { void render(Canvas canvas) { final debugText = [ 'FPS: ${gameRef.fps().toStringAsFixed(1)}', - 'BALLS: ${gameRef.descendants().whereType().length}', + 'BALLS: ${gameRef.descendants().whereType().length}', ].join(' | '); final height = _debugTextPaint.measureTextHeight(debugText); diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index 076ed336..354d61ed 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -7,7 +7,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:pinball/assets_manager/assets_manager.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/gen/gen.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/more_information/more_information.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; @@ -21,59 +23,44 @@ class PinballGamePage extends StatelessWidget { final bool isDebugMode; - static Route route({ - bool isDebugMode = kDebugMode, - }) { + static Route route({bool isDebugMode = kDebugMode}) { return MaterialPageRoute( - builder: (context) { - return PinballGamePage( - isDebugMode: isDebugMode, - ); - }, + builder: (_) => PinballGamePage(isDebugMode: isDebugMode), ); } @override Widget build(BuildContext context) { - final characterTheme = - context.read().state.characterTheme; + final characterThemeBloc = context.read(); final player = context.read(); final leaderboardRepository = context.read(); + final gameBloc = context.read(); + final game = isDebugMode + ? DebugPinballGame( + characterThemeBloc: characterThemeBloc, + player: player, + leaderboardRepository: leaderboardRepository, + l10n: context.l10n, + gameBloc: gameBloc, + ) + : PinballGame( + characterThemeBloc: characterThemeBloc, + player: player, + leaderboardRepository: leaderboardRepository, + l10n: context.l10n, + gameBloc: gameBloc, + ); + + final loadables = [ + ...game.preLoadAssets(), + ...player.load(), + ...BonusAnimation.loadAssets(), + ...SelectedCharacter.loadAssets(), + ]; return BlocProvider( - create: (_) => GameBloc(), - child: Builder( - builder: (context) { - final gameBloc = context.read(); - final game = isDebugMode - ? DebugPinballGame( - characterTheme: characterTheme, - player: player, - leaderboardRepository: leaderboardRepository, - l10n: context.l10n, - gameBloc: gameBloc, - ) - : PinballGame( - characterTheme: characterTheme, - player: player, - leaderboardRepository: leaderboardRepository, - l10n: context.l10n, - gameBloc: gameBloc, - ); - - final loadables = [ - ...game.preLoadAssets(), - ...player.load(), - ...BonusAnimation.loadAssets(), - ...SelectedCharacter.loadAssets(), - ]; - - return BlocProvider( - create: (_) => AssetsManagerCubit(loadables)..load(), - child: PinballGameView(game: game), - ); - }, - ), + create: (_) => AssetsManagerCubit(loadables)..load(), + child: PinballGameView(game: game), ); } } @@ -142,6 +129,7 @@ class PinballGameLoadedView extends StatelessWidget { ), ), const _PositionedGameHud(), + const _PositionedInfoIcon(), ], ), ); @@ -159,6 +147,7 @@ class _PositionedGameHud extends StatelessWidget { final isGameOver = context.select( (GameBloc bloc) => bloc.state.status.isGameOver, ); + final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16; final screenWidth = MediaQuery.of(context).size.width; final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8); @@ -174,3 +163,27 @@ class _PositionedGameHud extends StatelessWidget { ); } } + +class _PositionedInfoIcon extends StatelessWidget { + const _PositionedInfoIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + top: 0, + left: 0, + child: BlocBuilder( + builder: (context, state) { + return Visibility( + visible: state.status.isGameOver, + child: IconButton( + iconSize: 50, + icon: Assets.images.linkBox.infoIcon.image(), + onPressed: () => showMoreInformationDialog(context), + ), + ); + }, + ), + ); + } +} diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8175a899..e4e12788 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -104,22 +104,6 @@ "@toSubmit": { "description": "Ending text displayed on initials input screen informational text span" }, - "footerMadeWithText": "Made with ", - "@footerMadeWithText": { - "description": "Text shown on the footer which mentions technologies used to build the app." - }, - "footerFlutterLinkText": "Flutter", - "@footerFlutterLinkText": { - "description": "Text on the link shown on the footer which navigates to the Flutter page" - }, - "footerFirebaseLinkText": "Firebase", - "@footerFirebaseLinkText": { - "description": "Text on the link shown on the footer which navigates to the Firebase page" - }, - "footerGoogleIOText": "Google I/O", - "@footerGoogleIOText": { - "description": "Text shown on the footer which mentions Google I/O" - }, "linkBoxTitle": "Resources", "@linkBoxTitle": { "description": "Text shown on the link box title section." diff --git a/lib/more_information/more_information.dart b/lib/more_information/more_information.dart new file mode 100644 index 00000000..317461ed --- /dev/null +++ b/lib/more_information/more_information.dart @@ -0,0 +1 @@ +export 'more_information_dialog.dart'; diff --git a/lib/more_information/more_information_dialog.dart b/lib/more_information/more_information_dialog.dart new file mode 100644 index 00000000..179c06f5 --- /dev/null +++ b/lib/more_information/more_information_dialog.dart @@ -0,0 +1,218 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// Inflates [MoreInformationDialog] using [showDialog]. +Future showMoreInformationDialog(BuildContext context) { + final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16; + + return showDialog( + context: context, + barrierColor: PinballColors.transparent, + barrierDismissible: true, + builder: (_) { + return Center( + child: SizedBox( + height: gameWidgetWidth * 0.87, + width: gameWidgetWidth, + child: const MoreInformationDialog(), + ), + ); + }, + ); +} + +/// {@template more_information_dialog} +/// Dialog used to show informational links +/// {@endtemplate} +class MoreInformationDialog extends StatelessWidget { + /// {@macro more_information_dialog} + const MoreInformationDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: PinballColors.transparent, + child: _LinkBoxDecoration( + child: Column( + children: const [ + SizedBox(height: 16), + _LinkBoxHeader(), + Expanded( + child: _LinkBoxBody(), + ), + ], + ), + ), + ); + } +} + +class _LinkBoxHeader extends StatelessWidget { + const _LinkBoxHeader({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final indent = MediaQuery.of(context).size.width / 5; + + return Column( + children: [ + Text( + l10n.linkBoxTitle, + style: Theme.of(context).textTheme.headline3!.copyWith( + color: PinballColors.blue, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 12), + Divider( + color: PinballColors.white, + endIndent: indent, + indent: indent, + thickness: 2, + ), + ], + ); + } +} + +class _LinkBoxDecoration extends StatelessWidget { + const _LinkBoxDecoration({ + Key? key, + required this.child, + }) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: const CrtBackground().copyWith( + borderRadius: const BorderRadius.all(Radius.circular(12)), + border: Border.all( + color: PinballColors.white, + width: 5, + ), + ), + child: child, + ); + } +} + +class _LinkBoxBody extends StatelessWidget { + const _LinkBoxBody({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const _MadeWithFlutterAndFirebase(), + _TextLink( + text: l10n.linkBoxOpenSourceCode, + link: _MoreInformationUrl.openSourceCode, + ), + _TextLink( + text: l10n.linkBoxGoogleIOText, + link: _MoreInformationUrl.googleIOEvent, + ), + _TextLink( + text: l10n.linkBoxFlutterGames, + link: _MoreInformationUrl.flutterGamesWebsite, + ), + _TextLink( + text: l10n.linkBoxHowItsMade, + link: _MoreInformationUrl.howItsMadeArticle, + ), + _TextLink( + text: l10n.linkBoxTermsOfService, + link: _MoreInformationUrl.termsOfService, + ), + _TextLink( + text: l10n.linkBoxPrivacyPolicy, + link: _MoreInformationUrl.privacyPolicy, + ), + ], + ); + } +} + +class _TextLink extends StatelessWidget { + const _TextLink({ + Key? key, + required this.text, + required this.link, + }) : super(key: key); + + final String text; + final String link; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return InkWell( + onTap: () => openLink(link), + child: Text( + text, + style: theme.textTheme.headline5!.copyWith( + color: PinballColors.white, + ), + overflow: TextOverflow.ellipsis, + ), + ); + } +} + +class _MadeWithFlutterAndFirebase extends StatelessWidget { + const _MadeWithFlutterAndFirebase({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final theme = Theme.of(context); + return RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: l10n.linkBoxMadeWithText, + style: theme.textTheme.headline5!.copyWith(color: PinballColors.white), + children: [ + TextSpan( + text: l10n.linkBoxFlutterLinkText, + recognizer: TapGestureRecognizer() + ..onTap = () => openLink(_MoreInformationUrl.flutterWebsite), + style: const TextStyle( + decoration: TextDecoration.underline, + ), + ), + const TextSpan(text: ' & '), + TextSpan( + text: l10n.linkBoxFirebaseLinkText, + recognizer: TapGestureRecognizer() + ..onTap = () => openLink(_MoreInformationUrl.firebaseWebsite), + style: theme.textTheme.headline5!.copyWith( + decoration: TextDecoration.underline, + ), + ), + ], + ), + ); + } +} + +abstract class _MoreInformationUrl { + static const flutterWebsite = 'https://flutter.dev'; + static const firebaseWebsite = 'https://firebase.google.com'; + static const openSourceCode = 'https://github.com/VGVentures/pinball'; + static const googleIOEvent = 'https://events.google.com/io/'; + static const flutterGamesWebsite = 'http://flutter.dev/games'; + static const howItsMadeArticle = + 'https://medium.com/flutter/i-o-pinball-powered-by-flutter-and-firebase-d22423f3f5d'; + static const termsOfService = 'https://policies.google.com/terms'; + static const privacyPolicy = 'https://policies.google.com/privacy'; +} diff --git a/lib/select_character/cubit/character_theme_cubit.dart b/lib/select_character/cubit/character_theme_cubit.dart index 84792a71..362db11a 100644 --- a/lib/select_character/cubit/character_theme_cubit.dart +++ b/lib/select_character/cubit/character_theme_cubit.dart @@ -1,5 +1,4 @@ // ignore_for_file: public_member_api_docs -// TODO(allisonryan0002): Document this section when the API is stable. import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; diff --git a/lib/select_character/cubit/character_theme_state.dart b/lib/select_character/cubit/character_theme_state.dart index a1669f69..2f0f22ea 100644 --- a/lib/select_character/cubit/character_theme_state.dart +++ b/lib/select_character/cubit/character_theme_state.dart @@ -1,5 +1,4 @@ // ignore_for_file: public_member_api_docs -// TODO(allisonryan0002): Document this section when the API is stable. part of 'character_theme_cubit.dart'; diff --git a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart index c522584c..b4bd9d63 100644 --- a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart +++ b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart @@ -44,31 +44,10 @@ class LeaderboardRepository { final tenthPositionScore = leaderboard[9].score; if (entry.score > tenthPositionScore) { await _saveScore(entry); - await _deleteScoresUnder(tenthPositionScore); } } } - /// Determines if the given [initials] are allowed. - Future areInitialsAllowed({required String initials}) async { - // Initials can only be three uppercase A-Z letters - final initialsRegex = RegExp(r'^[A-Z]{3}$'); - if (!initialsRegex.hasMatch(initials)) { - return false; - } - try { - final document = await _firebaseFirestore - .collection('prohibitedInitials') - .doc('list') - .get(); - final prohibitedInitials = - document.get('prohibitedInitials') as List; - return !prohibitedInitials.contains(initials); - } on Exception catch (error, stackTrace) { - throw FetchProhibitedInitialsException(error, stackTrace); - } - } - Future> _fetchLeaderboardSortedByScore() async { try { final querySnapshot = await _firebaseFirestore @@ -91,23 +70,6 @@ class LeaderboardRepository { throw AddLeaderboardEntryException(error, stackTrace); } } - - Future _deleteScoresUnder(int score) async { - try { - final querySnapshot = await _firebaseFirestore - .collection(_leaderboardCollectionName) - .where(_scoreFieldName, isLessThanOrEqualTo: score) - .get(); - final documents = querySnapshot.docs; - for (final document in documents) { - await document.reference.delete(); - } - } on LeaderboardDeserializationException { - rethrow; - } on Exception catch (error, stackTrace) { - throw DeleteLeaderboardException(error, stackTrace); - } - } } extension on List { diff --git a/packages/leaderboard_repository/lib/src/models/exceptions.dart b/packages/leaderboard_repository/lib/src/models/exceptions.dart index f709a27e..cdd9cf25 100644 --- a/packages/leaderboard_repository/lib/src/models/exceptions.dart +++ b/packages/leaderboard_repository/lib/src/models/exceptions.dart @@ -40,16 +40,6 @@ class FetchLeaderboardException extends LeaderboardException { : super(error, stackTrace); } -/// {@template delete_leaderboard_exception} -/// Exception thrown when failure occurs while deleting the leaderboard under -/// the tenth position. -/// {@endtemplate} -class DeleteLeaderboardException extends LeaderboardException { - /// {@macro fetch_top_10_leaderboard_exception} - const DeleteLeaderboardException(Object error, StackTrace stackTrace) - : super(error, stackTrace); -} - /// {@template add_leaderboard_entry_exception} /// Exception thrown when failure occurs while adding entry to leaderboard. /// {@endtemplate} @@ -58,12 +48,3 @@ class AddLeaderboardEntryException extends LeaderboardException { const AddLeaderboardEntryException(Object error, StackTrace stackTrace) : super(error, stackTrace); } - -/// {@template fetch_prohibited_initials_exception} -/// Exception thrown when failure occurs while fetching prohibited initials. -/// {@endtemplate} -class FetchProhibitedInitialsException extends LeaderboardException { - /// {@macro fetch_prohibited_initials_exception} - const FetchProhibitedInitialsException(Object error, StackTrace stackTrace) - : super(error, stackTrace); -} diff --git a/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart b/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart index d13a9940..89984c5b 100644 --- a/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart +++ b/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart @@ -21,9 +21,6 @@ class _MockQueryDocumentSnapshot extends Mock class _MockDocumentReference extends Mock implements DocumentReference> {} -class _MockDocumentSnapshot extends Mock - implements DocumentSnapshot> {} - void main() { group('LeaderboardRepository', () { late FirebaseFirestore firestore; @@ -245,73 +242,10 @@ void main() { ); }); - test( - 'throws DeleteLeaderboardException ' - 'when deleting scores outside the top 10 fails', () async { - final deleteQuery = _MockQuery(); - final deleteQuerySnapshot = _MockQuerySnapshot(); - final newScore = LeaderboardEntryData( - playerInitials: 'ABC', - score: 15000, - character: CharacterType.android, - ); - final leaderboardScores = [ - 10000, - 9500, - 9000, - 8500, - 8000, - 7500, - 7000, - 6500, - 6000, - 5500, - 5000, - ]; - final deleteDocumentSnapshots = [5500, 5000].map((score) { - final queryDocumentSnapshot = _MockQueryDocumentSnapshot(); - when(queryDocumentSnapshot.data).thenReturn({ - 'character': 'dash', - 'playerInitials': 'AAA', - 'score': score - }); - when(() => queryDocumentSnapshot.id).thenReturn('id$score'); - when(() => queryDocumentSnapshot.reference) - .thenReturn(documentReference); - return queryDocumentSnapshot; - }).toList(); - when(deleteQuery.get).thenAnswer((_) async => deleteQuerySnapshot); - when(() => deleteQuerySnapshot.docs) - .thenReturn(deleteDocumentSnapshots); - final queryDocumentSnapshots = leaderboardScores.map((score) { - final queryDocumentSnapshot = _MockQueryDocumentSnapshot(); - when(queryDocumentSnapshot.data).thenReturn({ - 'character': 'dash', - 'playerInitials': 'AAA', - 'score': score - }); - when(() => queryDocumentSnapshot.id).thenReturn('id$score'); - when(() => queryDocumentSnapshot.reference) - .thenReturn(documentReference); - return queryDocumentSnapshot; - }).toList(); - when( - () => collectionReference.where('score', isLessThanOrEqualTo: 5500), - ).thenAnswer((_) => deleteQuery); - when(() => documentReference.delete()).thenThrow(Exception('oops')); - when(() => querySnapshot.docs).thenReturn(queryDocumentSnapshots); - expect( - () => leaderboardRepository.addLeaderboardEntry(newScore), - throwsA(isA()), - ); - }); - test( 'saves the new score when there are more than 10 scores in the ' - 'leaderboard and the new score is higher than the lowest top 10, and ' - 'deletes the scores that are not in the top 10 anymore', () async { - final deleteQuery = _MockQuery(); - final deleteQuerySnapshot = _MockQuerySnapshot(); + 'leaderboard and the new score is higher than the lowest top 10', + () async { final newScore = LeaderboardEntryData( playerInitials: 'ABC', score: 15000, @@ -330,21 +264,6 @@ void main() { 5500, 5000, ]; - final deleteDocumentSnapshots = [5500, 5000].map((score) { - final queryDocumentSnapshot = _MockQueryDocumentSnapshot(); - when(queryDocumentSnapshot.data).thenReturn({ - 'character': 'dash', - 'playerInitials': 'AAA', - 'score': score - }); - when(() => queryDocumentSnapshot.id).thenReturn('id$score'); - when(() => queryDocumentSnapshot.reference) - .thenReturn(documentReference); - return queryDocumentSnapshot; - }).toList(); - when(deleteQuery.get).thenAnswer((_) async => deleteQuerySnapshot); - when(() => deleteQuerySnapshot.docs) - .thenReturn(deleteDocumentSnapshots); final queryDocumentSnapshots = leaderboardScores.map((score) { final queryDocumentSnapshot = _MockQueryDocumentSnapshot(); when(queryDocumentSnapshot.data).thenReturn({ @@ -357,105 +276,11 @@ void main() { .thenReturn(documentReference); return queryDocumentSnapshot; }).toList(); - when( - () => collectionReference.where('score', isLessThanOrEqualTo: 5500), - ).thenAnswer((_) => deleteQuery); - when(() => documentReference.delete()) - .thenAnswer((_) async => Future.value()); + when(() => querySnapshot.docs).thenReturn(queryDocumentSnapshots); await leaderboardRepository.addLeaderboardEntry(newScore); verify(() => collectionReference.add(newScore.toJson())).called(1); - verify(() => documentReference.delete()).called(2); - }); - }); - - group('areInitialsAllowed', () { - late LeaderboardRepository leaderboardRepository; - late CollectionReference> collectionReference; - late DocumentReference> documentReference; - late DocumentSnapshot> documentSnapshot; - - setUp(() async { - collectionReference = _MockCollectionReference(); - documentReference = _MockDocumentReference(); - documentSnapshot = _MockDocumentSnapshot(); - leaderboardRepository = LeaderboardRepository(firestore); - - when(() => firestore.collection('prohibitedInitials')) - .thenReturn(collectionReference); - when(() => collectionReference.doc('list')) - .thenReturn(documentReference); - when(() => documentReference.get()) - .thenAnswer((_) async => documentSnapshot); - when(() => documentSnapshot.get('prohibitedInitials')) - .thenReturn(['BAD']); - }); - - test('returns true if initials are three letters and allowed', () async { - final isUsernameAllowedResponse = - await leaderboardRepository.areInitialsAllowed( - initials: 'ABC', - ); - expect( - isUsernameAllowedResponse, - isTrue, - ); }); - - test( - 'returns false if initials are shorter than 3 characters', - () async { - final areInitialsAllowedResponse = - await leaderboardRepository.areInitialsAllowed(initials: 'AB'); - expect(areInitialsAllowedResponse, isFalse); - }, - ); - - test( - 'returns false if initials are longer than 3 characters', - () async { - final areInitialsAllowedResponse = - await leaderboardRepository.areInitialsAllowed(initials: 'ABCD'); - expect(areInitialsAllowedResponse, isFalse); - }, - ); - - test( - 'returns false if initials contain a lowercase letter', - () async { - final areInitialsAllowedResponse = - await leaderboardRepository.areInitialsAllowed(initials: 'AbC'); - expect(areInitialsAllowedResponse, isFalse); - }, - ); - - test( - 'returns false if initials contain a special character', - () async { - final areInitialsAllowedResponse = - await leaderboardRepository.areInitialsAllowed(initials: 'A@C'); - expect(areInitialsAllowedResponse, isFalse); - }, - ); - - test('returns false if initials are forbidden', () async { - final areInitialsAllowedResponse = - await leaderboardRepository.areInitialsAllowed(initials: 'BAD'); - expect(areInitialsAllowedResponse, isFalse); - }); - - test( - 'throws FetchProhibitedInitialsException when Exception occurs ' - 'when trying to retrieve information from firestore', - () async { - when(() => firestore.collection('prohibitedInitials')) - .thenThrow(Exception('oops')); - expect( - () => leaderboardRepository.areInitialsAllowed(initials: 'ABC'), - throwsA(isA()), - ); - }, - ); }); }); } diff --git a/packages/pinball_audio/assets/sfx/sparky.mp3 b/packages/pinball_audio/assets/sfx/sparky.mp3 new file mode 100644 index 00000000..14bf0ec6 Binary files /dev/null and b/packages/pinball_audio/assets/sfx/sparky.mp3 differ diff --git a/packages/pinball_audio/lib/gen/assets.gen.dart b/packages/pinball_audio/lib/gen/assets.gen.dart index 916906c4..08e83d87 100644 --- a/packages/pinball_audio/lib/gen/assets.gen.dart +++ b/packages/pinball_audio/lib/gen/assets.gen.dart @@ -14,13 +14,13 @@ class $AssetsMusicGen { class $AssetsSfxGen { const $AssetsSfxGen(); - String get afterLaunch => 'assets/sfx/after_launch.mp3'; String get bumperA => 'assets/sfx/bumper_a.mp3'; String get bumperB => 'assets/sfx/bumper_b.mp3'; String get gameOverVoiceOver => 'assets/sfx/game_over_voice_over.mp3'; String get google => 'assets/sfx/google.mp3'; String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3'; String get launcher => 'assets/sfx/launcher.mp3'; + String get sparky => 'assets/sfx/sparky.mp3'; } class Assets { diff --git a/packages/pinball_audio/lib/src/pinball_audio.dart b/packages/pinball_audio/lib/src/pinball_audio.dart index 56289417..95c993c5 100644 --- a/packages/pinball_audio/lib/src/pinball_audio.dart +++ b/packages/pinball_audio/lib/src/pinball_audio.dart @@ -25,6 +25,9 @@ enum PinballAudio { /// Launcher launcher, + + /// Sparky + sparky, } /// Defines the contract of the creation of an [AudioPool]. @@ -161,6 +164,11 @@ class PinballPlayer { playSingleAudio: _playSingleAudio, path: Assets.sfx.google, ), + PinballAudio.sparky: _SimplePlayAudio( + preCacheSingleAudio: _preCacheSingleAudio, + playSingleAudio: _playSingleAudio, + path: Assets.sfx.sparky, + ), PinballAudio.launcher: _SimplePlayAudio( preCacheSingleAudio: _preCacheSingleAudio, playSingleAudio: _playSingleAudio, diff --git a/packages/pinball_audio/test/src/pinball_audio_test.dart b/packages/pinball_audio/test/src/pinball_audio_test.dart index fdcd661b..39060eb2 100644 --- a/packages/pinball_audio/test/src/pinball_audio_test.dart +++ b/packages/pinball_audio/test/src/pinball_audio_test.dart @@ -141,6 +141,10 @@ void main() { () => preCacheSingleAudio .onCall('packages/pinball_audio/assets/sfx/google.mp3'), ).called(1); + verify( + () => preCacheSingleAudio + .onCall('packages/pinball_audio/assets/sfx/sparky.mp3'), + ).called(1); verify( () => preCacheSingleAudio.onCall( 'packages/pinball_audio/assets/sfx/io_pinball_voice_over.mp3', @@ -211,7 +215,7 @@ void main() { }); }); - group('googleBonus', () { + group('google', () { test('plays the correct file', () async { await Future.wait(player.load()); player.play(PinballAudio.google); @@ -223,6 +227,18 @@ void main() { }); }); + group('sparky', () { + test('plays the correct file', () async { + await Future.wait(player.load()); + player.play(PinballAudio.sparky); + + verify( + () => playSingleAudio + .onCall('packages/pinball_audio/${Assets.sfx.sparky}'), + ).called(1); + }); + }); + group('launcher', () { test('plays the correct file', () async { await Future.wait(player.load()); diff --git a/packages/pinball_components/assets/images/flipper/left.png b/packages/pinball_components/assets/images/flipper/left.png index 3aefa225..190b5bb0 100644 Binary files a/packages/pinball_components/assets/images/flipper/left.png and b/packages/pinball_components/assets/images/flipper/left.png differ diff --git a/packages/pinball_components/assets/images/flipper/right.png b/packages/pinball_components/assets/images/flipper/right.png index 3627c86c..6bc46580 100644 Binary files a/packages/pinball_components/assets/images/flipper/right.png and b/packages/pinball_components/assets/images/flipper/right.png differ diff --git a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart index edce2a78..7024f65c 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart @@ -93,8 +93,6 @@ class AndroidBumper extends BodyComponent with InitialPosition, ZIndex { /// Creates an [AndroidBumper] without any children. /// /// This can be used for testing [AndroidBumper]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting AndroidBumper.test({ required this.bloc, @@ -105,8 +103,6 @@ class AndroidBumper extends BodyComponent with InitialPosition, ZIndex { final double _minorRadius; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final AndroidBumperCubit bloc; diff --git a/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart b/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart index d15a5516..c4cacc00 100644 --- a/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart +++ b/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart @@ -38,16 +38,12 @@ class AndroidSpaceship extends Component { /// Creates an [AndroidSpaceship] without any children. /// /// This can be used for testing [AndroidSpaceship]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting AndroidSpaceship.test({ required this.bloc, Iterable? children, }) : super(children: children); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 final AndroidSpaceshipCubit bloc; @override @@ -129,7 +125,6 @@ class _SpaceshipSaucerSpriteAnimationComponent extends SpriteAnimationComponent } } -// TODO(allisonryan0002): add pulsing behavior. class _LightBeamSpriteComponent extends SpriteComponent with HasGameRef, ZIndex { _LightBeamSpriteComponent() diff --git a/packages/pinball_components/lib/src/components/ball/ball.dart b/packages/pinball_components/lib/src/components/ball/ball.dart index e8cea997..af7a0361 100644 --- a/packages/pinball_components/lib/src/components/ball/ball.dart +++ b/packages/pinball_components/lib/src/components/ball/ball.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -8,27 +9,27 @@ import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; export 'behaviors/behaviors.dart'; +export 'cubit/ball_cubit.dart'; /// {@template ball} /// A solid, [BodyType.dynamic] sphere that rolls and bounces around. /// {@endtemplate} class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { /// {@macro ball} - Ball({ - String? assetPath, - }) : super( + Ball({String? assetPath}) : this._(bloc: BallCubit(), assetPath: assetPath); + + Ball._({required this.bloc, String? assetPath}) + : super( renderBody: false, children: [ - _BallSpriteComponent(assetPath: assetPath), + FlameBlocProvider.value( + value: bloc, + children: [BallSpriteComponent(assetPath: assetPath)], + ), BallScalingBehavior(), BallGravitatingBehavior(), ], ) { - // TODO(ruimiguel): while developing Ball can be launched by clicking mouse, - // and default layer is Layer.all. But on final game Ball will be always be - // be launched from Plunger and LauncherRamp will modify it to Layer.board. - // We need to see what happens if Ball appears from other place like nest - // bumper, it will need to explicit change layer to Layer.board then. layer = Layer.board; } @@ -36,11 +37,22 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { /// /// This can be used for testing [Ball]'s behaviors in isolation. @visibleForTesting - Ball.test() - : super( - children: [_BallSpriteComponent()], + Ball.test({ + BallCubit? bloc, + String? assetPath, + }) : bloc = bloc ?? BallCubit(), + super( + children: [ + FlameBlocProvider.value( + value: bloc ?? BallCubit(), + children: [BallSpriteComponent(assetPath: assetPath)], + ) + ], ); + /// Bloc to update the ball sprite when a new character is selected. + final BallCubit bloc; + /// The size of the [Ball]. static final Vector2 size = Vector2.all(4.13); @@ -60,7 +72,6 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { /// /// The [Ball] will no longer be affected by any forces, including it's /// weight and those emitted from collisions. - // TODO(allisonryan0002): prevent motion from contact with other balls. void stop() { body ..gravityScale = Vector2.zero() @@ -76,21 +87,32 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { } } -class _BallSpriteComponent extends SpriteComponent with HasGameRef { - _BallSpriteComponent({ - this.assetPath, - }) : super( - anchor: Anchor.center, - ); +/// {@template ball_sprite_component} +/// Visual representation of the [Ball]. +/// {@endtemplate} +@visibleForTesting +class BallSpriteComponent extends SpriteComponent + with HasGameRef, FlameBlocListenable { + /// {@macro ball_sprite_component} + BallSpriteComponent({required String? assetPath}) + : _assetPath = assetPath, + super(anchor: Anchor.center); + + final String? _assetPath; - final String? assetPath; + @override + void onNewState(BallState state) { + sprite = Sprite( + gameRef.images.fromCache(state.characterTheme.ball.keyName), + ); + } @override Future onLoad() async { await super.onLoad(); final sprite = Sprite( gameRef.images - .fromCache(assetPath ?? theme.Assets.images.dash.ball.keyName), + .fromCache(_assetPath ?? theme.Assets.images.dash.ball.keyName), ); this.sprite = sprite; size = sprite.originalSize / 12.5; diff --git a/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart b/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart index 7fc06fb1..e1e7c405 100644 --- a/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart +++ b/packages/pinball_components/lib/src/components/ball/behaviors/ball_scaling_behavior.dart @@ -16,9 +16,12 @@ class BallScalingBehavior extends Component with ParentIsA { parent.body.fixtures.first.shape.radius = (Ball.size.x / 2) * scaleFactor; - parent.firstChild()!.scale.setValues( - scaleFactor, - scaleFactor, - ); + final ballSprite = parent.descendants().whereType(); + if (ballSprite.isNotEmpty) { + ballSprite.single.scale.setValues( + scaleFactor, + scaleFactor, + ); + } } } diff --git a/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart b/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart new file mode 100644 index 00000000..e3054e33 --- /dev/null +++ b/packages/pinball_components/lib/src/components/ball/cubit/ball_cubit.dart @@ -0,0 +1,15 @@ +// ignore_for_file: public_member_api_docs + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +part 'ball_state.dart'; + +class BallCubit extends Cubit { + BallCubit() : super(const BallState.initial()); + + void onThemeChanged(CharacterTheme characterTheme) { + emit(BallState(characterTheme: characterTheme)); + } +} diff --git a/packages/pinball_components/lib/src/components/ball/cubit/ball_state.dart b/packages/pinball_components/lib/src/components/ball/cubit/ball_state.dart new file mode 100644 index 00000000..17a7aa9d --- /dev/null +++ b/packages/pinball_components/lib/src/components/ball/cubit/ball_state.dart @@ -0,0 +1,14 @@ +// ignore_for_file: public_member_api_docs + +part of 'ball_cubit.dart'; + +class BallState extends Equatable { + const BallState({required this.characterTheme}); + + const BallState.initial() : this(characterTheme: const DashTheme()); + + final CharacterTheme characterTheme; + + @override + List get props => [characterTheme]; +} diff --git a/packages/pinball_components/lib/src/components/board_dimensions.dart b/packages/pinball_components/lib/src/components/board_dimensions.dart index 3d547996..4f994f73 100644 --- a/packages/pinball_components/lib/src/components/board_dimensions.dart +++ b/packages/pinball_components/lib/src/components/board_dimensions.dart @@ -5,7 +5,6 @@ import 'package:flame/extensions.dart'; /// {@template board_dimensions} /// Contains various board properties and dimensions for global use. /// {@endtemplate} -// TODO(allisonryan0002): consider alternatives for global dimensions. class BoardDimensions { /// Width and height of the board. static final size = Vector2(101.6, 143.8); diff --git a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart index eff84ff4..a8435ec0 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior.dart @@ -14,7 +14,7 @@ class ChromeDinoChompingBehavior extends ContactBehavior { super.beginContact(other, contact); if (other is! Ball) return; - other.firstChild()!.setOpacity(0); + other.descendants().whereType().single.setOpacity(0); parent.bloc.onChomp(other); } } diff --git a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart index 78a8b9d5..876dd4d6 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior.dart @@ -30,7 +30,7 @@ class ChromeDinoSpittingBehavior extends Component void _spit() { parent.bloc.state.ball! - ..firstChild()!.setOpacity(1) + ..descendants().whereType().single.setOpacity(1) ..body.linearVelocity = Vector2(-50, 0); parent.bloc.onSpit(); } diff --git a/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart b/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart index 38a335b9..b7358767 100644 --- a/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart +++ b/packages/pinball_components/lib/src/components/chrome_dino/chrome_dino.dart @@ -38,15 +38,11 @@ class ChromeDino extends BodyComponent /// Creates a [ChromeDino] without any children. /// /// This can be used for testing [ChromeDino]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting ChromeDino.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final ChromeDinoCubit bloc; @@ -61,13 +57,13 @@ class ChromeDino extends BodyComponent List _createFixtureDefs() { const mouthAngle = -(halfSweepingAngle + 0.28); - final size = Vector2(5.5, 6); + final size = Vector2(6, 6); final topEdge = PolygonShape() ..setAsBox( size.x / 2, 0.1, - initialPosition + Vector2(-4.2, -1.4), + initialPosition + Vector2(-4, -1.4), mouthAngle, ); final topEdgeFixtureDef = FixtureDef(topEdge, density: 100); @@ -76,7 +72,7 @@ class ChromeDino extends BodyComponent ..setAsBox( 0.1, size.y / 2, - initialPosition + Vector2(-1.3, 0.5), + initialPosition + Vector2(-1, 0.5), -halfSweepingAngle, ); final backEdgeFixtureDef = FixtureDef(backEdge, density: 100); @@ -85,7 +81,7 @@ class ChromeDino extends BodyComponent ..setAsBox( size.x / 2, 0.1, - initialPosition + Vector2(-3.5, 4.7), + initialPosition + Vector2(-3.3, 4.7), mouthAngle, ); final bottomEdgeFixtureDef = FixtureDef( @@ -110,7 +106,7 @@ class ChromeDino extends BodyComponent ..setAsBox( 0.2, 0.2, - initialPosition + Vector2(-3.5, 1.5), + initialPosition + Vector2(-3, 1.5), 0, ); final insideSensorFixtureDef = FixtureDef( diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 55fe6bb5..54345772 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -36,5 +36,5 @@ export 'spaceship_rail.dart'; export 'spaceship_ramp/spaceship_ramp.dart'; export 'sparky_animatronic.dart'; export 'sparky_bumper/sparky_bumper.dart'; -export 'sparky_computer.dart'; +export 'sparky_computer/sparky_computer.dart'; export 'z_indexes.dart'; diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper/dash_nest_bumper.dart b/packages/pinball_components/lib/src/components/dash_nest_bumper/dash_nest_bumper.dart index 4495053d..b203d1f3 100644 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper/dash_nest_bumper.dart +++ b/packages/pinball_components/lib/src/components/dash_nest_bumper/dash_nest_bumper.dart @@ -90,8 +90,6 @@ class DashNestBumper extends BodyComponent with InitialPosition { /// Creates an [DashNestBumper] without any children. /// /// This can be used for testing [DashNestBumper]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting DashNestBumper.test({required this.bloc}) : _majorRadius = 3, @@ -100,8 +98,6 @@ class DashNestBumper extends BodyComponent with InitialPosition { final double _majorRadius; final double _minorRadius; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final DashNestBumperCubit bloc; diff --git a/packages/pinball_components/lib/src/components/fire_effect.dart b/packages/pinball_components/lib/src/components/fire_effect.dart index e793b3e6..77f3dd02 100644 --- a/packages/pinball_components/lib/src/components/fire_effect.dart +++ b/packages/pinball_components/lib/src/components/fire_effect.dart @@ -8,14 +8,6 @@ import 'package:flutter/material.dart'; const _particleRadius = 0.25; -// TODO(erickzanardo): This component could just be a ParticleComponet, -/// unfortunately there is a Particle Component is not a PositionComponent, -/// which makes it hard to be used since we have camera transformations and on -// top of that, PositionComponent has a bug inside forge 2d games -/// -/// https://github.com/flame-engine/flame/issues/1484 -/// https://github.com/flame-engine/flame/issues/1484 - /// {@template fire_effect} /// A [BodyComponent] which creates a fire trail effect using the given /// parameters diff --git a/packages/pinball_components/lib/src/components/flipper.dart b/packages/pinball_components/lib/src/components/flipper.dart index b62d2390..ca033440 100644 --- a/packages/pinball_components/lib/src/components/flipper.dart +++ b/packages/pinball_components/lib/src/components/flipper.dart @@ -100,8 +100,8 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition { final trapezium = PolygonShape()..set(trapeziumVertices); final trapeziumFixtureDef = FixtureDef( trapezium, - density: 50, // TODO(alestiago): Use a proper density. - friction: .1, // TODO(alestiago): Use a proper friction. + density: 50, + friction: .1, ); return [ diff --git a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart index 16218fa3..f447f6af 100644 --- a/packages/pinball_components/lib/src/components/google_letter/google_letter.dart +++ b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart @@ -68,15 +68,11 @@ class GoogleLetter extends BodyComponent with InitialPosition { /// Creates a [GoogleLetter] without any children. /// /// This can be used for testing [GoogleLetter]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting GoogleLetter.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final GoogleLetterCubit bloc; diff --git a/packages/pinball_components/lib/src/components/initial_position.dart b/packages/pinball_components/lib/src/components/initial_position.dart index 4265a3a7..1e9b6046 100644 --- a/packages/pinball_components/lib/src/components/initial_position.dart +++ b/packages/pinball_components/lib/src/components/initial_position.dart @@ -24,8 +24,6 @@ mixin InitialPosition on BodyComponent { @override Future onLoad() async { await super.onLoad(); - // TODO(alestiago): Investiagate why body.position.setFrom(initialPosition) - // works for some components and not others. assert( body.position == initialPosition, 'Body position does not match initialPosition.', diff --git a/packages/pinball_components/lib/src/components/kicker/kicker.dart b/packages/pinball_components/lib/src/components/kicker/kicker.dart index 1a45ad60..7bddd9eb 100644 --- a/packages/pinball_components/lib/src/components/kicker/kicker.dart +++ b/packages/pinball_components/lib/src/components/kicker/kicker.dart @@ -51,16 +51,12 @@ class Kicker extends BodyComponent with InitialPosition { /// Creates a [Kicker] without any children. /// /// This can be used for testing [Kicker]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Kicker.test({ required this.bloc, required BoardSide side, }) : _side = side; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final KickerCubit bloc; @@ -129,7 +125,6 @@ class Kicker extends BodyComponent with InitialPosition { FixtureDef(bouncyEdge, userData: 'bouncy_edge'), ]; - // TODO(alestiago): Evaluate if there is value on centering the fixtures. final centroid = geometry.centroid( [ upperCircle.position + Vector2(0, -upperCircle.radius), @@ -177,8 +172,6 @@ class _KickerSpriteGroupComponent extends SpriteGroupComponent @override Future onLoad() async { await super.onLoad(); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs parent.bloc.stream.listen((state) => current = state); @@ -203,8 +196,6 @@ class _KickerSpriteGroupComponent extends SpriteGroupComponent } } -// TODO(alestiago): Evaluate if there's value on generalising this to -// all shapes. extension on Shape { void moveBy(Vector2 offset) { if (this is CircleShape) { diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index e8290cff..815c5d4c 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -32,9 +32,6 @@ class _LaunchRampBase extends BodyComponent with Layered, ZIndex { layer = Layer.launcher; } - // TODO(ruimiguel): final asset differs slightly from the current shape. We - // need to fix shape with correct vertices, but right now merge them to have - // final assets at game and not be blocked. List _createFixtureDefs() { final fixturesDef = []; diff --git a/packages/pinball_components/lib/src/components/layer.dart b/packages/pinball_components/lib/src/components/layer.dart index 8418fac1..8b93a4b2 100644 --- a/packages/pinball_components/lib/src/components/layer.dart +++ b/packages/pinball_components/lib/src/components/layer.dart @@ -74,7 +74,6 @@ extension LayerMaskBits on Layer { /// {@macro layer_mask_bits} @visibleForTesting int get maskBits { - // TODO(ruialonso): test bit groups once final design is implemented. switch (this) { case Layer.all: return 0xFFFF; diff --git a/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart b/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart index 4b1d6ae3..68e0f076 100644 --- a/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart +++ b/packages/pinball_components/lib/src/components/layer_sensor/layer_sensor.dart @@ -50,8 +50,6 @@ abstract class LayerSensor extends BodyComponent with InitialPosition, Layered { Shape get shape; /// {@macro layer_entrance_orientation} - // TODO(ruimiguel): Try to remove the need of [LayerEntranceOrientation] for - // collision calculations. final LayerEntranceOrientation orientation; @override diff --git a/packages/pinball_components/lib/src/components/multiball/multiball.dart b/packages/pinball_components/lib/src/components/multiball/multiball.dart index 663490ca..8f75a901 100644 --- a/packages/pinball_components/lib/src/components/multiball/multiball.dart +++ b/packages/pinball_components/lib/src/components/multiball/multiball.dart @@ -75,15 +75,11 @@ class Multiball extends Component { /// Creates an [Multiball] without any children. /// /// This can be used for testing [Multiball]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Multiball.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final MultiballCubit bloc; diff --git a/packages/pinball_components/lib/src/components/multiplier/multiplier.dart b/packages/pinball_components/lib/src/components/multiplier/multiplier.dart index 54d02857..f749f912 100644 --- a/packages/pinball_components/lib/src/components/multiplier/multiplier.dart +++ b/packages/pinball_components/lib/src/components/multiplier/multiplier.dart @@ -81,8 +81,6 @@ class Multiplier extends Component { /// Creates a [Multiplier] without any children. /// /// This can be used for testing [Multiplier]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Multiplier.test({ required MultiplierValue value, @@ -91,8 +89,6 @@ class Multiplier extends Component { _position = Vector2.zero(), _angle = 0; -// TODO(ruimiguel): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 final MultiplierCubit bloc; final MultiplierValue _value; diff --git a/packages/pinball_components/lib/src/components/plunger.dart b/packages/pinball_components/lib/src/components/plunger.dart index 5b9b77b2..44a91b0b 100644 --- a/packages/pinball_components/lib/src/components/plunger.dart +++ b/packages/pinball_components/lib/src/components/plunger.dart @@ -178,26 +178,16 @@ class _PlungerSpriteAnimationGroupComponent @override Future onLoad() async { await super.onLoad(); - - // TODO(alestiago): Used cached images. final spriteSheet = await gameRef.images.load( Assets.images.plunger.plunger.keyName, ); - const amountPerRow = 20; const amountPerColumn = 1; - final textureSize = Vector2( spriteSheet.width / amountPerRow, spriteSheet.height / amountPerColumn, ); size = textureSize / 10; - - // TODO(ruimiguel): we only need plunger pull animation, and release is just - // to reverse it, so we need to divide by 2 while we don't have only half of - // the animation (but amountPerRow and amountPerColumn needs to be correct - // in order of calculate textureSize correctly). - final pullAnimation = SpriteAnimation.fromFrameData( spriteSheet, SpriteAnimationData.sequenced( @@ -209,7 +199,6 @@ class _PlungerSpriteAnimationGroupComponent loop: false, ), ); - animations = { _PlungerAnimationState.release: pullAnimation.reversed(), _PlungerAnimationState.pull: pullAnimation, diff --git a/packages/pinball_components/lib/src/components/shapes/arc_shape.dart b/packages/pinball_components/lib/src/components/shapes/arc_shape.dart index 8b31e223..59519f0d 100644 --- a/packages/pinball_components/lib/src/components/shapes/arc_shape.dart +++ b/packages/pinball_components/lib/src/components/shapes/arc_shape.dart @@ -28,7 +28,6 @@ class ArcShape extends ChainShape { final Vector2 center; /// The radius of the arc. - // TODO(alestiago): Check if modifying the parent radius makes sense. final double arcRadius; /// Specifies the size of the arc, in radians. diff --git a/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart b/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart index bfa7d435..92898cad 100644 --- a/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart +++ b/packages/pinball_components/lib/src/components/shapes/ellipse_shape.dart @@ -26,7 +26,6 @@ class EllipseShape extends ChainShape { /// The top left corner of the ellipse. /// /// Where the initial painting begins. - // TODO(ruialonso): Change to use appropiate center. final Vector2 center; /// Major radius is specified by [majorRadius]. diff --git a/packages/pinball_components/lib/src/components/signpost/signpost.dart b/packages/pinball_components/lib/src/components/signpost/signpost.dart index d22f46f3..a8fffe79 100644 --- a/packages/pinball_components/lib/src/components/signpost/signpost.dart +++ b/packages/pinball_components/lib/src/components/signpost/signpost.dart @@ -36,15 +36,11 @@ class Signpost extends BodyComponent with InitialPosition { /// Creates a [Signpost] without any children. /// /// This can be used for testing [Signpost]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting Signpost.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final SignpostCubit bloc; diff --git a/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart b/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart index 3bf10a7e..3daed592 100644 --- a/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart +++ b/packages/pinball_components/lib/src/components/skill_shot/skill_shot.dart @@ -38,15 +38,11 @@ class SkillShot extends BodyComponent with ZIndex { /// Creates a [SkillShot] without any children. /// /// This can be used for testing [SkillShot]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting SkillShot.test({ required this.bloc, }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final SkillShotCubit bloc; diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart index 0b407517..8fb5200e 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp/spaceship_ramp.dart @@ -27,8 +27,6 @@ class SpaceshipRamp extends Component { required this.bloc, }) : super( children: [ - // TODO(ruimiguel): refactor RampScoringSensor and - // _SpaceshipRampOpening to be in only one sensor if possible. RampScoringSensor( children: [ RampBallAscendingContactBehavior(), @@ -68,8 +66,6 @@ class SpaceshipRamp extends Component { required this.bloc, }) : super(); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final SpaceshipRampCubit bloc; diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart b/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart index b909f0ba..c4cb672a 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart @@ -93,8 +93,6 @@ class SparkyBumper extends BodyComponent with InitialPosition, ZIndex { /// Creates an [SparkyBumper] without any children. /// /// This can be used for testing [SparkyBumper]'s behaviors in isolation. - // TODO(alestiago): Refactor injecting bloc once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 @visibleForTesting SparkyBumper.test({ required this.bloc, @@ -104,8 +102,6 @@ class SparkyBumper extends BodyComponent with InitialPosition, ZIndex { final double _majorRadius; final double _minorRadius; - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs final SparkyBumperCubit bloc; @@ -152,8 +148,6 @@ class _SparkyBumperSpriteGroupComponent @override Future onLoad() async { await super.onLoad(); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs parent.bloc.stream.listen((state) => current = state); diff --git a/packages/pinball_components/lib/src/components/sparky_computer/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/sparky_computer/behaviors/behaviors.dart new file mode 100644 index 00000000..7befc568 --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_computer/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'sparky_computer_sensor_ball_contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/sparky_computer/behaviors/sparky_computer_sensor_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/sparky_computer/behaviors/sparky_computer_sensor_ball_contact_behavior.dart new file mode 100644 index 00000000..8e83f61f --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_computer/behaviors/sparky_computer_sensor_ball_contact_behavior.dart @@ -0,0 +1,35 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template sparky_computer_sensor_ball_contact_behavior} +/// When a [Ball] enters the [SparkyComputer] it is stopped for a period of time +/// before a [BallTurboChargingBehavior] is applied to it. +/// {@endtemplate} +class SparkyComputerSensorBallContactBehavior + extends ContactBehavior { + @override + Future beginContact(Object other, Contact contact) async { + super.beginContact(other, contact); + if (other is! Ball) return; + + other.stop(); + parent.bloc.onBallEntered(); + await parent.add( + TimerComponent( + period: 1.5, + removeOnFinish: true, + onTick: () async { + other.resume(); + await other.add( + BallTurboChargingBehavior( + impulse: Vector2(40, 110), + ), + ); + parent.bloc.onBallTurboCharged(); + }, + ), + ); + } +} diff --git a/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_cubit.dart b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_cubit.dart new file mode 100644 index 00000000..e86defcd --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_cubit.dart @@ -0,0 +1,17 @@ +// ignore_for_file: public_member_api_docs + +import 'package:bloc/bloc.dart'; + +part 'sparky_computer_state.dart'; + +class SparkyComputerCubit extends Cubit { + SparkyComputerCubit() : super(SparkyComputerState.withoutBall); + + void onBallEntered() { + emit(SparkyComputerState.withBall); + } + + void onBallTurboCharged() { + emit(SparkyComputerState.withoutBall); + } +} diff --git a/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_state.dart b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_state.dart new file mode 100644 index 00000000..372f1d15 --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_computer/cubit/sparky_computer_state.dart @@ -0,0 +1,8 @@ +// ignore_for_file: public_member_api_docs + +part of 'sparky_computer_cubit.dart'; + +enum SparkyComputerState { + withoutBall, + withBall, +} diff --git a/packages/pinball_components/lib/src/components/sparky_computer.dart b/packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart similarity index 66% rename from packages/pinball_components/lib/src/components/sparky_computer.dart rename to packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart index 8e2fc905..c96e3f45 100644 --- a/packages/pinball_components/lib/src/components/sparky_computer.dart +++ b/packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart @@ -2,31 +2,48 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/sparky_computer/behaviors/behaviors.dart'; import 'package:pinball_flame/pinball_flame.dart'; +export 'cubit/sparky_computer_cubit.dart'; + /// {@template sparky_computer} /// A computer owned by Sparky. /// {@endtemplate} -class SparkyComputer extends Component { +class SparkyComputer extends BodyComponent { /// {@macro sparky_computer} - SparkyComputer() - : super( + SparkyComputer({Iterable? children}) + : bloc = SparkyComputerCubit(), + super( + renderBody: false, children: [ - _ComputerBase(), + SparkyComputerSensorBallContactBehavior() + ..applyTo(['turbo_charge_sensor']), + _ComputerBaseSpriteComponent(), _ComputerTopSpriteComponent(), _ComputerGlowSpriteComponent(), + ...?children, ], ); -} -class _ComputerBase extends BodyComponent with InitialPosition, ZIndex { - _ComputerBase() - : super( - renderBody: false, - children: [_ComputerBaseSpriteComponent()], - ) { - zIndex = ZIndexes.computerBase; + /// Creates a [SparkyComputer] without any children. + /// + /// This can be used for testing [SparkyComputer]'s behaviors in isolation. + @visibleForTesting + SparkyComputer.test({ + required this.bloc, + Iterable? children, + }) : super(children: children); + + // ignore: public_member_api_docs + final SparkyComputerCubit bloc; + + @override + void onRemove() { + bloc.close(); + super.onRemove(); } List _createFixtureDefs() { @@ -45,30 +62,44 @@ class _ComputerBase extends BodyComponent with InitialPosition, ZIndex { topEdge.vertex2, Vector2(-9.4, -47.1), ); + final turboChargeSensor = PolygonShape() + ..setAsBox( + 1, + 0.1, + Vector2(-13.2, -49.9), + -0.18, + ); return [ FixtureDef(leftEdge), FixtureDef(topEdge), FixtureDef(rightEdge), + FixtureDef( + turboChargeSensor, + isSensor: true, + userData: 'turbo_charge_sensor', + ), ]; } @override Body createBody() { - final bodyDef = BodyDef(position: initialPosition); - final body = world.createBody(bodyDef); + final body = world.createBody(BodyDef()); _createFixtureDefs().forEach(body.createFixture); return body; } } -class _ComputerBaseSpriteComponent extends SpriteComponent with HasGameRef { +class _ComputerBaseSpriteComponent extends SpriteComponent + with HasGameRef, ZIndex { _ComputerBaseSpriteComponent() : super( anchor: Anchor.center, position: Vector2(-12.44, -48.15), - ); + ) { + zIndex = ZIndexes.computerBase; + } @override Future onLoad() async { diff --git a/packages/pinball_components/lib/src/components/z_indexes.dart b/packages/pinball_components/lib/src/components/z_indexes.dart index 88447312..e9fb68c9 100644 --- a/packages/pinball_components/lib/src/components/z_indexes.dart +++ b/packages/pinball_components/lib/src/components/z_indexes.dart @@ -1,7 +1,6 @@ // ignore_for_file: public_member_api_docs /// Z-Indexes for the component rendering order in the pinball game. -// TODO(allisonryan0002): find alternative to section comments. abstract class ZIndexes { static const _base = 0; static const _above = 1; @@ -21,8 +20,6 @@ abstract class ZIndexes { // Background - // TODO(allisonryan0002): fix this magic zindex. Could bump all priorities so - // there are no negatives. static const boardBackground = 5 * _below + _base; static const decal = _above + boardBackground; diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index a51ba799..473f565e 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -9,9 +9,10 @@ environment: dependencies: bloc: ^8.0.3 flame: ^1.1.1 + flame_bloc: ^1.4.0 flame_forge2d: git: - url: https://github.com/flame-engine/flame/ + url: https://github.com/flame-engine/flame path: packages/flame_forge2d/ ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: diff --git a/packages/pinball_components/sandbox/pubspec.lock b/packages/pinball_components/sandbox/pubspec.lock index 5e57d926..b5ac88b7 100644 --- a/packages/pinball_components/sandbox/pubspec.lock +++ b/packages/pinball_components/sandbox/pubspec.lock @@ -106,13 +106,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + flame_bloc: + dependency: transitive + description: + name: flame_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" flame_forge2d: dependency: "direct main" description: path: "packages/flame_forge2d" ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f resolved-ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f - url: "https://github.com/flame-engine/flame/" + url: "https://github.com/flame-engine/flame" source: git version: "0.11.0" flutter: @@ -120,6 +127,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: transitive + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.1" flutter_colorpicker: dependency: transitive description: @@ -214,6 +228,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" ordered_set: dependency: transitive description: @@ -298,13 +319,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" - share_repository: + provider: dependency: transitive description: - path: "../../share_repository" - relative: true - source: path - version: "1.0.0+1" + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.2" shared_preferences: dependency: transitive description: diff --git a/packages/pinball_components/sandbox/pubspec.yaml b/packages/pinball_components/sandbox/pubspec.yaml index d663cb04..791020d0 100644 --- a/packages/pinball_components/sandbox/pubspec.yaml +++ b/packages/pinball_components/sandbox/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flame: ^1.1.1 flame_forge2d: git: - url: https://github.com/flame-engine/flame/ + url: https://github.com/flame-engine/flame path: packages/flame_forge2d/ ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: diff --git a/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart b/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart index aaca08fc..b6b99f32 100644 --- a/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart +++ b/packages/pinball_components/test/src/components/android_bumper/android_bumper_test.dart @@ -44,8 +44,6 @@ void main() { expect(game.contains(androidBumper), isTrue); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockAndroidBumperCubit(); diff --git a/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart b/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart index 1b672be4..7e456a47 100644 --- a/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart +++ b/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart @@ -70,8 +70,6 @@ void main() { }, ); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockAndroidSpaceshipCubit(); diff --git a/packages/pinball_components/test/src/components/ball/ball_test.dart b/packages/pinball_components/test/src/components/ball/ball_test.dart index 9195e0b2..43454342 100644 --- a/packages/pinball_components/test/src/components/ball/ball_test.dart +++ b/packages/pinball_components/test/src/components/ball/ball_test.dart @@ -1,5 +1,6 @@ // ignore_for_file: cascade_invocations +import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -39,6 +40,41 @@ void main() { }, ); + flameTester.test( + 'has only one SpriteComponent', + (game) async { + final ball = Ball(); + await game.ready(); + await game.ensureAdd(ball); + + expect( + ball.descendants().whereType().length, + equals(1), + ); + }, + ); + + flameTester.test( + 'BallSpriteComponent changes sprite onNewState', + (game) async { + final ball = Ball(); + await game.ready(); + await game.ensureAdd(ball); + + final ballSprite = + ball.descendants().whereType().single; + final originalSprite = ballSprite.sprite; + + ballSprite.onNewState( + const BallState(characterTheme: theme.DinoTheme()), + ); + await game.ready(); + + final newSprite = ballSprite.sprite; + expect(newSprite != originalSprite, isTrue); + }, + ); + group('adds', () { flameTester.test('a BallScalingBehavior', (game) async { final ball = Ball(); diff --git a/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart b/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart index bd0cca49..c4c66d4a 100644 --- a/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart +++ b/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart @@ -62,8 +62,8 @@ void main() { await game.ensureAddAll([ball1, ball2]); game.update(1); - final sprite1 = ball1.firstChild()!; - final sprite2 = ball2.firstChild()!; + final sprite1 = ball1.descendants().whereType().single; + final sprite2 = ball2.descendants().whereType().single; expect( sprite1.scale.x, greaterThan(sprite2.scale.x), diff --git a/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart b/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart new file mode 100644 index 00000000..c5a03213 --- /dev/null +++ b/packages/pinball_components/test/src/components/ball/cubit/ball_cubit_test.dart @@ -0,0 +1,18 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group( + 'BallCubit', + () { + blocTest( + 'onThemeChanged emits new theme', + build: BallCubit.new, + act: (bloc) => bloc.onThemeChanged(const DinoTheme()), + expect: () => [const BallState(characterTheme: DinoTheme())], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/ball/cubit/ball_state_test.dart b/packages/pinball_components/test/src/components/ball/cubit/ball_state_test.dart new file mode 100644 index 00000000..1163ba1e --- /dev/null +++ b/packages/pinball_components/test/src/components/ball/cubit/ball_state_test.dart @@ -0,0 +1,22 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +void main() { + group('BallState', () { + test('supports value equality', () { + expect( + BallState(characterTheme: DashTheme()), + equals(const BallState(characterTheme: DashTheme())), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect(const BallState(characterTheme: DashTheme()), isNotNull); + }); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart index dfc33967..f5fd2b42 100644 --- a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart +++ b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_chomping_behavior_test.dart @@ -61,7 +61,10 @@ void main() { behavior.beginContact(ball, contact); - expect(ball.firstChild()!.getOpacity(), isZero); + expect( + ball.descendants().whereType().single.getOpacity(), + isZero, + ); verify(() => bloc.onChomp(ball)).called(1); }, diff --git a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart index 8c2cbe57..0748040e 100644 --- a/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart +++ b/packages/pinball_components/test/src/components/chrome_dino/behaviors/chrome_dino_spitting_behavior_test.dart @@ -66,7 +66,14 @@ void main() { .timer .onTick!(); - expect(ball.firstChild()!.getOpacity(), equals(1)); + expect( + ball + .descendants() + .whereType() + .single + .getOpacity(), + equals(1), + ); expect(ball.body.linearVelocity, equals(Vector2(-50, 0))); }, ); diff --git a/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart b/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart index d6366092..603f6ca0 100644 --- a/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart +++ b/packages/pinball_components/test/src/components/chrome_dino/chrome_dino_test.dart @@ -71,8 +71,6 @@ void main() { }, ); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockChromeDinoCubit(); diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png index eaeb458e..a84d84c2 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/down.png differ diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png index d8665644..0515f5f5 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/middle.png differ diff --git a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png index a584b785..0a2d4674 100644 Binary files a/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png and b/packages/pinball_components/test/src/components/chrome_dino/golden/chrome_dino/up.png differ diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/dash_nest_bumper_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/dash_nest_bumper_test.dart index 195231bf..0ee1d6b9 100644 --- a/packages/pinball_components/test/src/components/dash_nest_bumper/dash_nest_bumper_test.dart +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/dash_nest_bumper_test.dart @@ -46,8 +46,6 @@ void main() { expect(game.contains(bumper), isTrue); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockDashNestBumperCubit(); diff --git a/packages/pinball_components/test/src/components/flipper_test.dart b/packages/pinball_components/test/src/components/flipper_test.dart index 314b1f77..53b0e108 100644 --- a/packages/pinball_components/test/src/components/flipper_test.dart +++ b/packages/pinball_components/test/src/components/flipper_test.dart @@ -18,8 +18,6 @@ void main() { final flameTester = FlameTester(() => TestGame(assets)); group('Flipper', () { - // TODO(alestiago): Consider testing always both left and right Flipper. - flameTester.testGameWidget( 'renders correctly', setUp: (game, tester) async { diff --git a/packages/pinball_components/test/src/components/golden/flipper.png b/packages/pinball_components/test/src/components/golden/flipper.png index 07fe81ed..8507618d 100644 Binary files a/packages/pinball_components/test/src/components/golden/flipper.png and b/packages/pinball_components/test/src/components/golden/flipper.png differ diff --git a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart b/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart index afd4c130..3a459ddb 100644 --- a/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart +++ b/packages/pinball_components/test/src/components/google_letter/google_letter_test.dart @@ -102,8 +102,6 @@ void main() { expect(() => GoogleLetter(6), throwsA(isA())); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockGoogleLetterCubit(); diff --git a/packages/pinball_components/test/src/components/kicker_test.dart b/packages/pinball_components/test/src/components/kicker_test.dart index 4d3fc14d..c91046ba 100644 --- a/packages/pinball_components/test/src/components/kicker_test.dart +++ b/packages/pinball_components/test/src/components/kicker_test.dart @@ -61,8 +61,6 @@ void main() { }, ); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockKickerCubit(); diff --git a/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart b/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart index dabacc69..4ec42a4b 100644 --- a/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart +++ b/packages/pinball_components/test/src/components/skill_shot/skill_shot_test.dart @@ -29,8 +29,6 @@ void main() { expect(game.contains(skillShot), isTrue); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockSkillShotCubit(); diff --git a/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart b/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart index 7544fdd2..65f0c647 100644 --- a/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart +++ b/packages/pinball_components/test/src/components/sparky_bumper/sparky_bumper_test.dart @@ -44,8 +44,6 @@ void main() { expect(game.contains(sparkyBumper), isTrue); }); - // TODO(alestiago): Consider refactoring once the following is merged: - // https://github.com/flame-engine/flame/pull/1538 // ignore: public_member_api_docs flameTester.test('closes bloc when removed', (game) async { final bloc = _MockSparkyBumperCubit(); diff --git a/packages/pinball_components/test/src/components/sparky_computer/behaviors/sparky_computer_sensor_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/sparky_computer/behaviors/sparky_computer_sensor_ball_contact_behavior_test.dart new file mode 100644 index 00000000..d90cc2c9 --- /dev/null +++ b/packages/pinball_components/test/src/components/sparky_computer/behaviors/sparky_computer_sensor_ball_contact_behavior_test.dart @@ -0,0 +1,141 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/sparky_computer/behaviors/behaviors.dart'; + +import '../../../../helpers/helpers.dart'; + +class _MockSparkyComputerCubit extends Mock implements SparkyComputerCubit {} + +class _MockBall extends Mock implements Ball {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + + group( + 'SparkyComputerSensorBallContactBehavior', + () { + test('can be instantiated', () { + expect( + SparkyComputerSensorBallContactBehavior(), + isA(), + ); + }); + + group('beginContact', () { + flameTester.test( + 'stops a ball', + (game) async { + final behavior = SparkyComputerSensorBallContactBehavior(); + final bloc = _MockSparkyComputerCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: SparkyComputerState.withoutBall, + ); + + final sparkyComputer = SparkyComputer.test( + bloc: bloc, + ); + await sparkyComputer.add(behavior); + await game.ensureAdd(sparkyComputer); + + final ball = _MockBall(); + await behavior.beginContact(ball, _MockContact()); + + verify(ball.stop).called(1); + }, + ); + + flameTester.test( + 'emits onBallEntered when contacts with a ball', + (game) async { + final behavior = SparkyComputerSensorBallContactBehavior(); + final bloc = _MockSparkyComputerCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: SparkyComputerState.withoutBall, + ); + + final sparkyComputer = SparkyComputer.test( + bloc: bloc, + ); + await sparkyComputer.add(behavior); + await game.ensureAdd(sparkyComputer); + + await behavior.beginContact(_MockBall(), _MockContact()); + + verify(sparkyComputer.bloc.onBallEntered).called(1); + }, + ); + + flameTester.test( + 'adds TimerComponent when contacts with a ball', + (game) async { + final behavior = SparkyComputerSensorBallContactBehavior(); + final bloc = _MockSparkyComputerCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: SparkyComputerState.withoutBall, + ); + + final sparkyComputer = SparkyComputer.test( + bloc: bloc, + ); + await sparkyComputer.add(behavior); + await game.ensureAdd(sparkyComputer); + + await behavior.beginContact(_MockBall(), _MockContact()); + await game.ready(); + + expect( + sparkyComputer.firstChild(), + isA(), + ); + }, + ); + + flameTester.test( + 'TimerComponent resumes ball and calls onBallTurboCharged onTick', + (game) async { + final behavior = SparkyComputerSensorBallContactBehavior(); + final bloc = _MockSparkyComputerCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: SparkyComputerState.withoutBall, + ); + + final sparkyComputer = SparkyComputer.test( + bloc: bloc, + ); + await sparkyComputer.add(behavior); + await game.ensureAdd(sparkyComputer); + + final ball = _MockBall(); + await behavior.beginContact(ball, _MockContact()); + await game.ready(); + game.update( + sparkyComputer.firstChild()!.timer.limit, + ); + await game.ready(); + + verify(ball.resume).called(1); + verify(sparkyComputer.bloc.onBallTurboCharged).called(1); + }, + ); + }); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/sparky_computer/cubit/sparky_computer_cubit_test.dart b/packages/pinball_components/test/src/components/sparky_computer/cubit/sparky_computer_cubit_test.dart new file mode 100644 index 00000000..b08b412f --- /dev/null +++ b/packages/pinball_components/test/src/components/sparky_computer/cubit/sparky_computer_cubit_test.dart @@ -0,0 +1,24 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group( + 'SparkyComputerCubit', + () { + blocTest( + 'onBallEntered emits withBall', + build: SparkyComputerCubit.new, + act: (bloc) => bloc.onBallEntered(), + expect: () => [SparkyComputerState.withBall], + ); + + blocTest( + 'onBallTurboCharged emits withoutBall', + build: SparkyComputerCubit.new, + act: (bloc) => bloc.onBallTurboCharged(), + expect: () => [SparkyComputerState.withoutBall], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart b/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart new file mode 100644 index 00000000..dcbccd3e --- /dev/null +++ b/packages/pinball_components/test/src/components/sparky_computer/sparky_computer_test.dart @@ -0,0 +1,91 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/sparky_computer/behaviors/behaviors.dart'; + +import '../../../helpers/helpers.dart'; + +class _MockSparkyComputerCubit extends Mock implements SparkyComputerCubit {} + +void main() { + group('SparkyComputer', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.sparky.computer.base.keyName, + Assets.images.sparky.computer.top.keyName, + Assets.images.sparky.computer.glow.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + flameTester.test('loads correctly', (game) async { + final component = SparkyComputer(); + await game.ensureAdd(component); + expect(game.contains(component), isTrue); + }); + + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.loadAll(assets); + await game.ensureAdd(SparkyComputer()); + await tester.pump(); + + game.camera + ..followVector2(Vector2(0, -20)) + ..zoom = 7; + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile('../golden/sparky-computer.png'), + ); + }, + ); + + // ignore: public_member_api_docs + flameTester.test('closes bloc when removed', (game) async { + final bloc = _MockSparkyComputerCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: SparkyComputerState.withoutBall, + ); + when(bloc.close).thenAnswer((_) async {}); + final sparkyComputer = SparkyComputer.test(bloc: bloc); + + await game.ensureAdd(sparkyComputer); + game.remove(sparkyComputer); + await game.ready(); + + verify(bloc.close).called(1); + }); + + group('adds', () { + flameTester.test('new children', (game) async { + final component = Component(); + final sparkyComputer = SparkyComputer( + children: [component], + ); + await game.ensureAdd(sparkyComputer); + expect(sparkyComputer.children, contains(component)); + }); + + flameTester.test('a SparkyComputerSensorBallContactBehavior', + (game) async { + final sparkyComputer = SparkyComputer(); + await game.ensureAdd(sparkyComputer); + expect( + sparkyComputer.children + .whereType() + .single, + isNotNull, + ); + }); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/sparky_computer_test.dart b/packages/pinball_components/test/src/components/sparky_computer_test.dart deleted file mode 100644 index ffba79b6..00000000 --- a/packages/pinball_components/test/src/components/sparky_computer_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/pinball_components.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - group('SparkyComputer', () { - TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.sparky.computer.base.keyName, - Assets.images.sparky.computer.top.keyName, - Assets.images.sparky.computer.glow.keyName, - ]; - final flameTester = FlameTester(() => TestGame(assets)); - - flameTester.test('loads correctly', (game) async { - final component = SparkyComputer(); - await game.ensureAdd(component); - expect(game.contains(component), isTrue); - }); - - flameTester.testGameWidget( - 'renders correctly', - setUp: (game, tester) async { - await game.images.loadAll(assets); - await game.ensureAdd(SparkyComputer()); - await tester.pump(); - - game.camera - ..followVector2(Vector2(0, -20)) - ..zoom = 7; - }, - verify: (game, tester) async { - await expectLater( - find.byGame(), - matchesGoldenFile('golden/sparky-computer.png'), - ); - }, - ); - }); -} diff --git a/packages/pinball_flame/lib/src/canvas/z_canvas_component.dart b/packages/pinball_flame/lib/src/canvas/z_canvas_component.dart index e097f359..e149bf58 100644 --- a/packages/pinball_flame/lib/src/canvas/z_canvas_component.dart +++ b/packages/pinball_flame/lib/src/canvas/z_canvas_component.dart @@ -1,4 +1,5 @@ import 'dart:ui'; +import 'package:collection/collection.dart' as collection; import 'package:flame/components.dart'; import 'package:pinball_flame/src/canvas/canvas_wrapper.dart'; @@ -56,7 +57,14 @@ class _ZCanvas extends CanvasWrapper { final List _zBuffer = []; /// Postpones the rendering of [ZIndex] component and its children. - void buffer(ZIndex component) => _zBuffer.add(component); + void buffer(ZIndex component) { + final lowerBound = collection.lowerBound( + _zBuffer, + component, + compare: (a, b) => a.zIndex.compareTo(b.zIndex), + ); + _zBuffer.insert(lowerBound, component); + } /// Renders all [ZIndex] components and their children. /// @@ -69,8 +77,7 @@ class _ZCanvas extends CanvasWrapper { /// before the second one. /// {@endtemplate} void render() => _zBuffer - ..sort((a, b) => a.zIndex.compareTo(b.zIndex)) - ..whereType().forEach(_render) + ..forEach(_render) ..clear(); void _render(Component component) => component.renderTree(canvas); diff --git a/packages/pinball_flame/lib/src/contact_behavior.dart b/packages/pinball_flame/lib/src/contact_behavior.dart index 92f108d8..855bb620 100644 --- a/packages/pinball_flame/lib/src/contact_behavior.dart +++ b/packages/pinball_flame/lib/src/contact_behavior.dart @@ -10,8 +10,6 @@ import 'package:pinball_flame/pinball_flame.dart'; /// /// It does so by grouping the userData in a [_UserData], and resetting the /// parent's userData accordingly. -// TODO(alestiago): Make use of generics to infer the type of the contact. -// https://github.com/VGVentures/pinball/pull/234#discussion_r859182267 class ContactBehavior extends Component with ContactCallbacks, ParentIsA { final _fixturesUserData = {}; diff --git a/packages/pinball_flame/lib/src/flame_provider.dart b/packages/pinball_flame/lib/src/flame_provider.dart index 35afb0a5..85a1004c 100644 --- a/packages/pinball_flame/lib/src/flame_provider.dart +++ b/packages/pinball_flame/lib/src/flame_provider.dart @@ -1,6 +1,8 @@ // ignore_for_file: public_member_api_docs +import 'package:bloc/bloc.dart'; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; class FlameProvider extends Component { FlameProvider.value( @@ -63,3 +65,15 @@ extension ReadFlameProvider on Component { return providers.first.provider; } } + +extension ReadFlameBlocProvider on Component { + B readBloc, S>() { + final providers = ancestors().whereType>(); + assert( + providers.isNotEmpty, + 'No FlameBlocProvider<$B, $S> available on the component tree', + ); + + return providers.first.bloc; + } +} diff --git a/packages/pinball_flame/lib/src/parent_is_a.dart b/packages/pinball_flame/lib/src/parent_is_a.dart index 19159c89..720a07b6 100644 --- a/packages/pinball_flame/lib/src/parent_is_a.dart +++ b/packages/pinball_flame/lib/src/parent_is_a.dart @@ -1,8 +1,5 @@ import 'package:flame/components.dart'; -// TODO(alestiago): Remove once the following is merged: -// https://github.com/flame-engine/flame/pull/1566 - /// A mixin that ensures a parent is of the given type [T]. mixin ParentIsA on Component { @override diff --git a/packages/pinball_flame/lib/src/sprite_animation.dart b/packages/pinball_flame/lib/src/sprite_animation.dart index 2990fb14..de39849f 100644 --- a/packages/pinball_flame/lib/src/sprite_animation.dart +++ b/packages/pinball_flame/lib/src/sprite_animation.dart @@ -9,8 +9,6 @@ import 'package:flutter/material.dart' hide Animation; /// {@template flame.widgets.sprite_animation_widget} /// A [StatelessWidget] that renders a [SpriteAnimation]. /// {@endtemplate} -// TODO(arturplaczek): Remove when this PR will be merged. -// https://github.com/flame-engine/flame/pull/1552 class SpriteAnimationWidget extends StatelessWidget { /// {@macro flame.widgets.sprite_animation_widget} const SpriteAnimationWidget({ diff --git a/packages/pinball_flame/pubspec.yaml b/packages/pinball_flame/pubspec.yaml index 89caf5bb..4639a080 100644 --- a/packages/pinball_flame/pubspec.yaml +++ b/packages/pinball_flame/pubspec.yaml @@ -7,10 +7,12 @@ environment: sdk: ">=2.16.0 <3.0.0" dependencies: + bloc: ^8.0.0 flame: ^1.1.1 + flame_bloc: ^1.4.0 flame_forge2d: git: - url: https://github.com/flame-engine/flame/ + url: https://github.com/flame-engine/flame path: packages/flame_forge2d/ ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: diff --git a/packages/pinball_flame/test/src/flame_provider_test.dart b/packages/pinball_flame/test/src/flame_provider_test.dart index cfc10613..71d69e0c 100644 --- a/packages/pinball_flame/test/src/flame_provider_test.dart +++ b/packages/pinball_flame/test/src/flame_provider_test.dart @@ -1,11 +1,15 @@ // ignore_for_file: cascade_invocations +import 'package:bloc/bloc.dart'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball_flame/pinball_flame.dart'; +class _FakeCubit extends Fake implements Cubit {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); final flameTester = FlameTester(FlameGame.new); @@ -100,4 +104,33 @@ void main() { ); }, ); + + group( + 'ReadFlameBlocProvider', + () { + flameTester.test('loads provider', (game) async { + final component = Component(); + final bloc = _FakeCubit(); + final provider = FlameBlocProvider<_FakeCubit, Object>.value( + value: bloc, + children: [component], + ); + await game.ensureAdd(provider); + expect(component.readBloc<_FakeCubit, Object>(), equals(bloc)); + }); + + flameTester.test( + 'throws assertionError when no provider is found', + (game) async { + final component = Component(); + await game.ensureAdd(component); + + expect( + () => component.readBloc<_FakeCubit, Object>(), + throwsAssertionError, + ); + }, + ); + }, + ); } diff --git a/packages/pinball_flame/test/src/sprite_animation_test.dart b/packages/pinball_flame/test/src/sprite_animation_test.dart index dc37d983..5483ee6e 100644 --- a/packages/pinball_flame/test/src/sprite_animation_test.dart +++ b/packages/pinball_flame/test/src/sprite_animation_test.dart @@ -10,9 +10,6 @@ class _MockSpriteAnimation extends Mock implements SpriteAnimation {} class _MockSprite extends Mock implements Sprite {} -// TODO(arturplaczek): Remove when this PR will be merged. -// https://github.com/flame-engine/flame/pull/1552 - void main() { group('PinballSpriteAnimationWidget', () { late SpriteAnimationController controller; diff --git a/packages/pinball_theme/assets/images/android/ball.png b/packages/pinball_theme/assets/images/android/ball.png index b5cfbc3f..ca2eebe3 100644 Binary files a/packages/pinball_theme/assets/images/android/ball.png and b/packages/pinball_theme/assets/images/android/ball.png differ diff --git a/packages/pinball_theme/assets/images/dash/ball.png b/packages/pinball_theme/assets/images/dash/ball.png index fa754cbc..69e8dffa 100644 Binary files a/packages/pinball_theme/assets/images/dash/ball.png and b/packages/pinball_theme/assets/images/dash/ball.png differ diff --git a/packages/pinball_theme/assets/images/dino/ball.png b/packages/pinball_theme/assets/images/dino/ball.png index 02b99c43..1a2102d8 100644 Binary files a/packages/pinball_theme/assets/images/dino/ball.png and b/packages/pinball_theme/assets/images/dino/ball.png differ diff --git a/packages/pinball_theme/assets/images/sparky/ball.png b/packages/pinball_theme/assets/images/sparky/ball.png index 95e5a10b..fe3f4e82 100644 Binary files a/packages/pinball_theme/assets/images/sparky/ball.png and b/packages/pinball_theme/assets/images/sparky/ball.png differ diff --git a/pubspec.lock b/pubspec.lock index f5c98153..d91f93db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -245,7 +245,7 @@ packages: path: "packages/flame_forge2d" ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f resolved-ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f - url: "https://github.com/flame-engine/flame/" + url: "https://github.com/flame-engine/flame" source: git version: "0.11.0" flame_test: @@ -828,4 +828,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.16.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=2.10.5" diff --git a/pubspec.yaml b/pubspec.yaml index 14a32348..8a76c48d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,6 +5,7 @@ publish_to: none environment: sdk: ">=2.16.0 <3.0.0" + flutter: 2.10.5 dependencies: authentication_repository: @@ -18,7 +19,7 @@ dependencies: flame_bloc: ^1.4.0 flame_forge2d: git: - url: https://github.com/flame-engine/flame/ + url: https://github.com/flame-engine/flame path: packages/flame_forge2d/ ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f flutter: diff --git a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart b/test/game/behaviors/ball_spawning_behavior_test.dart similarity index 90% rename from test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart rename to test/game/behaviors/ball_spawning_behavior_test.dart index f41487cd..d723c65e 100644 --- a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart +++ b/test/game/behaviors/ball_spawning_behavior_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/behaviors/ball_spawning_behavior.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; @@ -18,18 +19,20 @@ class _TestGame extends Forge2DGame { } Future pump( - Iterable children, { + List children, { GameBloc? gameBloc, }) async { await ensureAdd( - FlameBlocProvider.value( - value: gameBloc ?? GameBloc(), - children: [ - FlameProvider.value( - const theme.DashTheme(), - children: children, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: gameBloc ?? GameBloc(), + ), + FlameBlocProvider.value( + value: CharacterThemeCubit(), ), ], + children: children, ), ); } diff --git a/test/game/behaviors/ball_theming_behavior_test.dart b/test/game/behaviors/ball_theming_behavior_test.dart new file mode 100644 index 00000000..0c1e0b36 --- /dev/null +++ b/test/game/behaviors/ball_theming_behavior_test.dart @@ -0,0 +1,93 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/select_character/select_character.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; +import 'package:pinball_theme/pinball_theme.dart' as theme; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + theme.Assets.images.dash.ball.keyName, + theme.Assets.images.dino.ball.keyName, + ]); + } + + Future pump( + List children, { + CharacterThemeCubit? characterThemeBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: characterThemeBloc ?? CharacterThemeCubit(), + children: children, + ), + ); + } +} + +class _MockBallCubit extends Mock implements BallCubit {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'BallThemingBehavior', + () { + final flameTester = FlameTester(_TestGame.new); + + test('can be instantiated', () { + expect( + BallThemingBehavior(), + isA(), + ); + }); + + flameTester.test( + 'loads', + (game) async { + final behavior = BallThemingBehavior(); + await game.pump([behavior]); + expect(game.descendants(), contains(behavior)); + }, + ); + + flameTester.test( + 'onNewState calls onThemeChanged on the ball bloc', + (game) async { + final ballBloc = _MockBallCubit(); + whenListen( + ballBloc, + const Stream.empty(), + initialState: const BallState.initial(), + ); + final ball = Ball.test(bloc: ballBloc); + final behavior = BallThemingBehavior(); + await game.pump([ + ball, + behavior, + ZCanvasComponent(), + Plunger.test(compressionDistance: 10), + ]); + + const dinoThemeState = CharacterThemeState(theme.DinoTheme()); + behavior.onNewState(dinoThemeState); + await game.ready(); + + verify(() => ballBloc.onThemeChanged(dinoThemeState.characterTheme)) + .called(1); + }, + ); + }, + ); +} diff --git a/test/game/behaviors/bonus_noise_behavior_test.dart b/test/game/behaviors/bonus_noise_behavior_test.dart new file mode 100644 index 00000000..12f62545 --- /dev/null +++ b/test/game/behaviors/bonus_noise_behavior_test.dart @@ -0,0 +1,183 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_audio/pinball_audio.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class _TestGame extends Forge2DGame { + Future pump( + BonusNoiseBehavior child, { + required PinballPlayer player, + required GameBloc bloc, + }) { + return ensureAdd( + FlameBlocProvider.value( + value: bloc, + children: [ + FlameProvider.value( + player, + children: [ + child, + ], + ), + ], + ), + ); + } +} + +class _MockPinballPlayer extends Mock implements PinballPlayer {} + +class _MockGameBloc extends Mock implements GameBloc {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('BonusNoiseBehavior', () { + late PinballPlayer player; + late GameBloc bloc; + final flameTester = FlameTester(_TestGame.new); + + setUpAll(() { + registerFallbackValue(PinballAudio.google); + }); + + setUp(() { + player = _MockPinballPlayer(); + when(() => player.play(any())).thenAnswer((_) {}); + bloc = _MockGameBloc(); + }); + + flameTester.testGameWidget( + 'plays google sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.googleWord], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + verify(() => player.play(PinballAudio.google)).called(1); + }, + ); + + flameTester.testGameWidget( + 'plays sparky sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.sparkyTurboCharge], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + verify(() => player.play(PinballAudio.sparky)).called(1); + }, + ); + + flameTester.testGameWidget( + 'plays dino chomp sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.dinoChomp], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + verifyNever(() => player.play(any())); + }, + ); + + flameTester.testGameWidget( + 'plays android spaceship sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.androidSpaceship], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + verifyNever(() => player.play(any())); + }, + ); + + flameTester.testGameWidget( + 'plays dash nest sound', + setUp: (game, _) async { + const state = GameState( + totalScore: 0, + roundScore: 0, + multiplier: 1, + rounds: 0, + bonusHistory: [GameBonus.dashNest], + status: GameStatus.playing, + ); + const initialState = GameState.initial(); + whenListen( + bloc, + Stream.fromIterable([initialState, state]), + initialState: initialState, + ); + final behavior = BonusNoiseBehavior(); + await game.pump(behavior, player: player, bloc: bloc); + }, + verify: (_, __) async { + verifyNever(() => player.play(any())); + }, + ); + }); +} diff --git a/test/game/components/controlled_ball_test.dart b/test/game/components/controlled_ball_test.dart deleted file mode 100644 index 95451515..00000000 --- a/test/game/components/controlled_ball_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:flame_bloc/flame_bloc.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; - -class _TestGame extends Forge2DGame { - @override - Future onLoad() async { - images.prefix = ''; - await images.load(theme.Assets.images.dash.ball.keyName); - } - - Future pump(Ball child, {required GameBloc gameBloc}) async { - await ensureAdd( - FlameBlocProvider.value( - value: gameBloc, - children: [child], - ), - ); - } -} - -class _MockGameBloc extends Mock implements GameBloc {} - -class _MockBall extends Mock implements Ball {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('BallController', () { - late Ball ball; - late GameBloc gameBloc; - - setUp(() { - ball = Ball(); - gameBloc = _MockGameBloc(); - }); - - final flameBlocTester = FlameTester(_TestGame.new); - - test('can be instantiated', () { - expect( - BallController(_MockBall()), - isA(), - ); - }); - - flameBlocTester.testGameWidget( - 'turboCharge adds TurboChargeActivated', - setUp: (game, tester) async { - await game.onLoad(); - - final controller = BallController(ball); - await ball.add(controller); - await game.pump(ball, gameBloc: gameBloc); - - await controller.turboCharge(); - }, - verify: (game, tester) async { - verify(() => gameBloc.add(const SparkyTurboChargeActivated())) - .called(1); - }, - ); - }); -} diff --git a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart index 3dcd870b..0d058c70 100644 --- a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart +++ b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; @@ -26,16 +27,16 @@ class _TestGame extends Forge2DGame { required GameBloc gameBloc, }) async { await ensureAdd( - FlameBlocProvider.value( - value: gameBloc, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value(value: gameBloc), + FlameBlocProvider.value( + value: CharacterThemeCubit(), + ), + ], children: [ - FlameProvider.value( - const theme.DashTheme(), - children: [ - ZCanvasComponent( - children: [child], - ), - ], + ZCanvasComponent( + children: [child], ), ], ), diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 7118aa8d..767ddefa 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -8,10 +8,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; class _TestGame extends Forge2DGame { @override @@ -25,18 +25,18 @@ class _TestGame extends Forge2DGame { PinballPlayer? pinballPlayer, }) async { return ensureAdd( - FlameBlocProvider.value( - value: GameBloc(), + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: GameBloc(), + ), + FlameBlocProvider.value( + value: CharacterThemeCubit(), + ), + ], children: [ - MultiFlameProvider( - providers: [ - FlameProvider.value( - pinballPlayer ?? _MockPinballPlayer(), - ), - FlameProvider.value( - const theme.DashTheme(), - ), - ], + FlameProvider.value( + pinballPlayer ?? _MockPinballPlayer(), children: children, ), ], diff --git a/test/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior_test.dart b/test/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior_test.dart new file mode 100644 index 00000000..fbfeef0b --- /dev/null +++ b/test/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior_test.dart @@ -0,0 +1,86 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/components/sparky_scorch/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.sparky.computer.top.keyName, + Assets.images.sparky.computer.base.keyName, + Assets.images.sparky.computer.glow.keyName, + Assets.images.sparky.animatronic.keyName, + Assets.images.sparky.bumper.a.lit.keyName, + Assets.images.sparky.bumper.a.dimmed.keyName, + Assets.images.sparky.bumper.b.lit.keyName, + Assets.images.sparky.bumper.b.dimmed.keyName, + Assets.images.sparky.bumper.c.lit.keyName, + Assets.images.sparky.bumper.c.dimmed.keyName, + ]); + } + + Future pump( + SparkyScorch child, { + required GameBloc gameBloc, + }) async { + // Not needed once https://github.com/flame-engine/flame/issues/1607 + // is fixed + await onLoad(); + await ensureAdd( + FlameBlocProvider.value( + value: gameBloc, + children: [child], + ), + ); + } +} + +class _MockGameBloc extends Mock implements GameBloc {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('SparkyComputerBonusBehavior', () { + late GameBloc gameBloc; + + setUp(() { + gameBloc = _MockGameBloc(); + }); + + final flameTester = FlameTester(_TestGame.new); + + flameTester.testGameWidget( + 'adds GameBonus.sparkyTurboCharge to the game and plays animatronic ' + 'when SparkyComputerState.withBall is emitted', + setUp: (game, tester) async { + final behavior = SparkyComputerBonusBehavior(); + final parent = SparkyScorch.test(); + final sparkyComputer = SparkyComputer(); + final animatronic = SparkyAnimatronic(); + + await parent.addAll([ + sparkyComputer, + animatronic, + ]); + await game.pump(parent, gameBloc: gameBloc); + await parent.ensureAdd(behavior); + + sparkyComputer.bloc.onBallEntered(); + await tester.pump(); + + verify( + () => gameBloc.add(const BonusActivated(GameBonus.sparkyTurboCharge)), + ).called(1); + expect(animatronic.playing, isTrue); + }, + ); + }); +} diff --git a/test/game/components/sparky_scorch_test.dart b/test/game/components/sparky_scorch/sparky_scorch_test.dart similarity index 58% rename from test/game/components/sparky_scorch_test.dart rename to test/game/components/sparky_scorch/sparky_scorch_test.dart index 92a3ab01..0cd7b806 100644 --- a/test/game/components/sparky_scorch_test.dart +++ b/test/game/components/sparky_scorch/sparky_scorch_test.dart @@ -1,10 +1,11 @@ // ignore_for_file: cascade_invocations +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/components/sparky_scorch/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -25,13 +26,16 @@ class _TestGame extends Forge2DGame { Assets.images.sparky.bumper.c.dimmed.keyName, ]); } -} - -class _MockControlledBall extends Mock implements ControlledBall {} - -class _MockBallController extends Mock implements BallController {} -class _MockContact extends Mock implements Contact {} + Future pump(SparkyScorch child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [child], + ), + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -41,15 +45,18 @@ void main() { group('SparkyScorch', () { flameTester.test('loads correctly', (game) async { final component = SparkyScorch(); - await game.ensureAdd(component); - expect(game.contains(component), isTrue); + await game.pump(component); + expect( + game.descendants().whereType().length, + equals(1), + ); }); group('loads', () { flameTester.test( 'a SparkyComputer', (game) async { - await game.ensureAdd(SparkyScorch()); + await game.pump(SparkyScorch()); expect( game.descendants().whereType().length, equals(1), @@ -60,7 +67,7 @@ void main() { flameTester.test( 'a SparkyAnimatronic', (game) async { - await game.ensureAdd(SparkyScorch()); + await game.pump(SparkyScorch()); expect( game.descendants().whereType().length, equals(1), @@ -71,7 +78,7 @@ void main() { flameTester.test( 'three SparkyBumper', (game) async { - await game.ensureAdd(SparkyScorch()); + await game.pump(SparkyScorch()); expect( game.descendants().whereType().length, equals(3), @@ -82,7 +89,7 @@ void main() { flameTester.test( 'three SparkyBumpers with BumperNoiseBehavior', (game) async { - await game.ensureAdd(SparkyScorch()); + await game.pump(SparkyScorch()); final bumpers = game.descendants().whereType(); for (final bumper in bumpers) { expect( @@ -93,41 +100,30 @@ void main() { }, ); }); - }); - - group('SparkyComputerSensor', () { - flameTester.test('calls turboCharge', (game) async { - final sensor = SparkyComputerSensor(); - final ball = _MockControlledBall(); - final controller = _MockBallController(); - when(() => ball.controller).thenReturn(controller); - when(controller.turboCharge).thenAnswer((_) async {}); - - await game.ensureAddAll([ - sensor, - SparkyAnimatronic(), - ]); - sensor.beginContact(ball, _MockContact()); - - verify(() => ball.controller.turboCharge()).called(1); - }); + group('adds', () { + flameTester.test( + 'ScoringContactBehavior to SparkyComputer', + (game) async { + await game.pump(SparkyScorch()); - flameTester.test('plays SparkyAnimatronic', (game) async { - final sensor = SparkyComputerSensor(); - final sparkyAnimatronic = SparkyAnimatronic(); - final ball = _MockControlledBall(); - final controller = _MockBallController(); - when(() => ball.controller).thenReturn(controller); - when(controller.turboCharge).thenAnswer((_) async {}); - await game.ensureAddAll([ - sensor, - sparkyAnimatronic, - ]); + final sparkyComputer = + game.descendants().whereType().single; + expect( + sparkyComputer.firstChild(), + isNotNull, + ); + }, + ); - expect(sparkyAnimatronic.playing, isFalse); - sensor.beginContact(ball, _MockContact()); - expect(sparkyAnimatronic.playing, isTrue); + flameTester.test('a SparkyComputerBonusBehavior', (game) async { + final sparkyScorch = SparkyScorch(); + await game.pump(sparkyScorch); + expect( + sparkyScorch.children.whereType().single, + isNotNull, + ); + }); }); }); } diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index b983b0b8..3b034c8f 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -13,14 +13,14 @@ import 'package:leaderboard_repository/src/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/select_character/select_character.dart'; import 'package:pinball_audio/src/pinball_audio.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; class _TestPinballGame extends PinballGame { _TestPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), @@ -39,7 +39,7 @@ class _TestPinballGame extends PinballGame { class _TestDebugPinballGame extends DebugPinballGame { _TestDebugPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), @@ -109,6 +109,17 @@ void main() { }, ); + flameTester.test( + 'has only one BallThemingBehavior', + (game) async { + await game.ready(); + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + flameTester.test( 'has only one Drain', (game) async { @@ -409,14 +420,12 @@ void main() { when(() => tapUpEvent.raw).thenReturn(raw); await game.ready(); - final previousBalls = - game.descendants().whereType().toList(); + final previousBalls = game.descendants().whereType().toList(); game.onTapUp(0, tapUpEvent); await game.ready(); - final currentBalls = - game.descendants().whereType().toList(); + final currentBalls = game.descendants().whereType().toList(); expect( currentBalls.length, @@ -475,14 +484,13 @@ void main() { game.lineEnd = endPosition; await game.ready(); - final previousBalls = - game.descendants().whereType().toList(); + final previousBalls = game.descendants().whereType().toList(); game.onPanEnd(_MockDragEndInfo()); await game.ready(); expect( - game.descendants().whereType().length, + game.descendants().whereType().length, equals(previousBalls.length + 1), ); }, diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index d1ecd72d..0e23e54d 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -10,18 +10,19 @@ import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/assets_manager/assets_manager.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/gen/gen.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/more_information/more_information.dart'; import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart'; import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_theme/pinball_theme.dart' as theme; import '../../helpers/helpers.dart'; class _TestPinballGame extends PinballGame { _TestPinballGame() : super( - characterTheme: const theme.DashTheme(), + characterThemeBloc: CharacterThemeCubit(), leaderboardRepository: _MockLeaderboardRepository(), gameBloc: GameBloc(), l10n: _MockAppLocalizations(), @@ -82,6 +83,7 @@ void main() { await tester.pumpApp( PinballGamePage(), characterThemeCubit: characterThemeCubit, + gameBloc: gameBloc, ); expect(find.byType(PinballGameView), findsOneWidget); @@ -170,6 +172,7 @@ void main() { ), ), characterThemeCubit: characterThemeCubit, + gameBloc: gameBloc, ); await tester.tap(find.text('Tap me')); @@ -331,5 +334,52 @@ void main() { expect(game.focusNode.hasFocus, isTrue); }); + + group('info icon', () { + testWidgets('renders on game over', (tester) async { + final gameState = GameState.initial().copyWith( + status: GameStatus.gameOver, + ); + + whenListen( + gameBloc, + Stream.value(gameState), + initialState: gameState, + ); + + await tester.pumpApp( + PinballGameView(game: game), + gameBloc: gameBloc, + startGameBloc: startGameBloc, + ); + + expect( + find.image(Assets.images.linkBox.infoIcon), + findsOneWidget, + ); + }); + + testWidgets('opens MoreInformationDialog when tapped', (tester) async { + final gameState = GameState.initial().copyWith( + status: GameStatus.gameOver, + ); + whenListen( + gameBloc, + Stream.value(gameState), + initialState: gameState, + ); + await tester.pumpApp( + PinballGameView(game: game), + gameBloc: gameBloc, + startGameBloc: startGameBloc, + ); + await tester.tap(find.byType(IconButton)); + await tester.pump(); + expect( + find.byType(MoreInformationDialog), + findsOneWidget, + ); + }); + }); }); } diff --git a/test/game/view/widgets/bonus_animation_test.dart b/test/game/view/widgets/bonus_animation_test.dart index 52c1b3d8..5f67e968 100644 --- a/test/game/view/widgets/bonus_animation_test.dart +++ b/test/game/view/widgets/bonus_animation_test.dart @@ -68,9 +68,6 @@ void main() { }); }); - // TODO(arturplaczek): refactor this test when there is a new version of the - // flame with an onComplete callback in SpriteAnimationWidget - // https://github.com/flame-engine/flame/issues/1543 testWidgets('called onCompleted callback at the end of animation ', (tester) async { final callback = _MockCallback(); diff --git a/test/game/view/widgets/game_hud_test.dart b/test/game/view/widgets/game_hud_test.dart index f4054146..f4fe4b89 100644 --- a/test/game/view/widgets/game_hud_test.dart +++ b/test/game/view/widgets/game_hud_test.dart @@ -135,13 +135,9 @@ void main() { Stream.value(state), initialState: initialState, ); - await _pumpAppWithWidget(tester); await tester.pump(); - // TODO(arturplaczek): remove magic number once this is merged: - // https://github.com/flame-engine/flame/pull/1564 await Future.delayed(const Duration(seconds: 6)); - await expectLater(find.byType(ScoreView), findsOneWidget); }); }, diff --git a/test/helpers/mock_flame_images.dart b/test/helpers/mock_flame_images.dart index 48e4d40e..891ede7f 100644 --- a/test/helpers/mock_flame_images.dart +++ b/test/helpers/mock_flame_images.dart @@ -14,8 +14,6 @@ class _MockImages extends Mock implements Images {} /// Using real images blocks the tests, for this reason we need fake image /// everywhere we use [Images.fromCache] or [Images.load]. /// {@endtemplate} -// TODO(arturplaczek): need to find for a better solution for loading image -// or use original images. Future mockFlameImages() async { final image = await decodeImageFromList(Uint8List.fromList(_fakeImage)); final images = _MockImages(); diff --git a/test/footer/footer_test.dart b/test/more_information/more_information_dialog_test.dart similarity index 60% rename from test/footer/footer_test.dart rename to test/more_information/more_information_dialog_test.dart index 8f683cbf..f87ec84c 100644 --- a/test/footer/footer_test.dart +++ b/test/more_information/more_information_dialog_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:pinball/footer/footer.dart'; +import 'package:pinball/more_information/more_information.dart'; import 'package:pinball_ui/pinball_ui.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; @@ -28,15 +28,34 @@ class _MockUrlLauncher extends Mock implements UrlLauncherPlatform {} void main() { - group('Footer', () { + group('MoreInformationDialog', () { late UrlLauncherPlatform urlLauncher; setUp(() async { urlLauncher = _MockUrlLauncher(); UrlLauncherPlatform.instance = urlLauncher; }); + + group('showMoreInformationDialog', () { + testWidgets('inflates the dialog', (tester) async { + await tester.pumpApp( + Builder( + builder: (context) { + return TextButton( + onPressed: () => showMoreInformationDialog(context), + child: const Text('test'), + ); + }, + ), + ); + await tester.tap(find.text('test')); + await tester.pump(); + expect(find.byType(MoreInformationDialog), findsOneWidget); + }); + }); + testWidgets('renders "Made with..." and "Google I/O"', (tester) async { - await tester.pumpApp(const Footer()); + await tester.pumpApp(const MoreInformationDialog()); expect(find.text('Google I/O'), findsOneWidget); expect( find.byWidgetPredicate( @@ -63,7 +82,7 @@ void main() { headers: any(named: 'headers'), ), ).thenAnswer((_) async => true); - await tester.pumpApp(const Footer()); + await tester.pumpApp(const MoreInformationDialog()); final flutterTextFinder = find.byWidgetPredicate( (widget) => widget is RichText && _tapTextSpan(widget, 'Flutter'), ); @@ -98,7 +117,7 @@ void main() { headers: any(named: 'headers'), ), ).thenAnswer((_) async => true); - await tester.pumpApp(const Footer()); + await tester.pumpApp(const MoreInformationDialog()); final firebaseTextFinder = find.byWidgetPredicate( (widget) => widget is RichText && _tapTextSpan(widget, 'Firebase'), ); @@ -117,5 +136,50 @@ void main() { ); }, ); + + { + 'Open Source Code': 'https://github.com/VGVentures/pinball', + 'Google I/O': 'https://events.google.com/io/', + 'Flutter Games': 'http://flutter.dev/games', + 'How it’s made': + 'https://medium.com/flutter/i-o-pinball-powered-by-flutter-and-firebase-d22423f3f5d', + 'Terms of Service': 'https://policies.google.com/terms', + 'Privacy Policy': 'https://policies.google.com/privacy', + }.forEach((text, link) { + testWidgets( + 'tapping on "$text" opens the link - $link', + (tester) async { + when(() => urlLauncher.canLaunch(any())) + .thenAnswer((_) async => true); + when( + () => urlLauncher.launch( + link, + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((_) async => true); + + await tester.pumpApp(const MoreInformationDialog()); + await tester.tap(find.text(text)); + await tester.pumpAndSettle(); + + verify( + () => urlLauncher.launch( + link, + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ); + }, + ); + }); }); }